Back to Schedule

Week 6: React Part 2


Props

Lists and Keys

State and Lifecycle

Events

Passing State Down to Child Components

Lifting State Up to Parent Components


Props

In React, props (a nickname for “properties”) are a way of passing data from a parent component to a child component. You can pass any value that can be stored in a JavaScript variable in a prop, by providing the property name and value as an attribute on the child element in JSX, like so:

class IceCream extends React.Component {
    render() {
        return (
            <p>My flavor is {this.props.flavor}.</p>
        );
    }
}

class IceCreamShop extends React.Component {
    render() {
        return (
            <div class="ice-creams">
                <IceCream flavor="chocolate" />
                <IceCream flavor="strawberry" />
                <IceCream flavor="vanilla" />
            </div>
        );
    }
}

ReactDOM.render(<IceCreamShop />, document.getElementById('root'));

In this example, we can observe the following:

  1. IceCreamShop is rendered by ReactDOM, and contains 3 IceCream components, so each IceCream component is rendered as well.
  2. The IceCream components are each passed a prop, flavor, in the IceCreamShop render method. The first IceCream, for example is constructed with a props object of { flavor: 'chocolate' }. How this happens behind the scenes is that each IceCream component extends React.Component, and React.Component constructor sets this.props to whichever attributes are defined on the element.
  3. Each ice cream displays its own flavor from this.props. The result is:
My flavor is chocolate.
My flavor is strawberry.
My flavor is vanilla.

Whenever React sees an element representing a component, it translates the attributes of the component to a single object called props. The most important thing to remember about props is that they are read-only and immutable. That is, a component should never modify the value of its props. The value passed in initially from the parent element should not be changed and treated as a constant.

Lists and Keys

We can also generate the same result as above without having to write out tags for every separate IceCream in IceCreamShop’s render method by storing the flavors in an array and using JavaScript’s Array.map function:

class IceCream extends React.Component {
    render() {
        return (
            <p>My flavor is {this.props.flavor}.</p>
        );
    }
}

class IceCreamShop extends React.Component {
    constructor(props) {
        super(props);
        this.flavors = ['chocolate', 'strawberry', 'vanilla'];
    }

    render() {
        return (
            <div class="ice-creams">
                {this.flavors.map((flavor, index) => {
                    return <IceCream key={index} flavor={flavor} />;
                })}
            </div>
        )
    }
}

ReactDOM.render(<IceCreamShop />, document.getElementById('root'));

What this map method is doing is saying, translate each flavor in this.flavors into an IceCream element with flavor equal to that flavor and key equal to that flavor’s index in the flavors array. key is a special React prop to differentiate multiple instances of the same component from one another. It will become very important, when you have these multiple instances, to differentiate each one with a unique key, so that React can tell them apart when trying to make updates.

You can find more information about array operations in React here: Lists and Keys

State and Lifecycle

Recall that the value of props in React can never be changed. How then, can we have dynamic components with changing values in React? The answer is a separate aspect of React components called state.

State is a separate but related concept to props, in that both state and props are owned by components, but state is not passed in directly from the parent, and it can change over time. While props describe permanent attributes that are assigned to a component, state describes how a set of malleable data in the component looks at a specific point in time.

To look at how state can change over time, let us look at the Clock example from last week’s prep. We can first reimplement our Clock and make it look nicer by adding date as a prop on the component:

class Clock extends React.Component {
  render() {
    return (
        <div>
            <h1>Hello, world!</h1>
            <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
    );
  }
}

function tick() {
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById('root')
    );
}

setInterval(tick, 1000);

While this implementation works, it would be nice if the Clock component could update itself from within the Clock class, instead of relying on the external method tick() to re-render its whole template in the DOM. Ideally, we want to be able to just do one ReactDOM.render and have the clock take care of it’s own updates from then on:

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

We can achieve this by incorporating the date in state, rather than in props. Every time there is a changing value, we should remember that state values can change and props cannot, and so decide to make it a state value. We accomplish this as below:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = { date: new Date() };
    }

    tick() {
        this.setState({ date: new Date() })
    }

    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}

As you can see, we assign the initial state of the component in the constructor, when the component is constructed. this.state is an object that can have any number of properties. Here, we have only date. To set a state variable later on, after the component has been initialized, we can use this.setState and provide an object that contains which property of state to update as well as its new value as an argument. An example of this is the function tick().

But this clock will still not work, because we are not calling the tick() method anywhere. What we need to do now is set up a timer that will run tick every second, like what we did previously. One issue is that we can’t set up this timer until we are sure that the component has rendered. Otherwise it will attempt to set state on something that does not exist.

To guarantee that the timer starts only after the component is fully set up, we can tap into one of React’s lifecycle methods, componentDidMount. This is a method defined by React.Component that will run once exactly after the component has “mounted” (or rendered). Similarly, we can use componentWillUnmount to destroy our timer if and when the component gets removed from the DOM:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

You can read more about this example and State and Lifecycle here: State and Lifecycle.

Events

Instead of using JavaScripts’ addEventListener() function like we did in past labs, in React, we will primarily be relying on event attributes directly on HTML elements. This is actually very close to how events work in native HTML. In pure HTML, you might define a keypress listener as so:

<input type="text" onkeypress="myKeyPressFunction()">

In JSX, we would simply change this to:

render() {
    return <input type="text" onKeyPress={this.myKeyPressFunction} />;
}

The main difference is that the key press callback function will be directly on the React Component class in which this render function is defined.

You may notice that the function is specified as this.myKeyPressFunction - what exactly is this in this instance? The code for the overall component class may look something like this:

class TextField extends React.Component {
    myKeyPressFunction() {
        console.log('key was pressed')
    }

    render() {
        return <input type="text" onKeyPress={this.myKeyPressFunction} />;
    }
}

We want to set this to equal the TextField class that the myKeyPress is in. However, the contents of the render function will not be linked to context of the class it is in unless we force the linking through a binding. It is important to remember that in JavaScript, functions will not be bound by default. If we want to the this from inside the render to refer to the class it is in, we must manually bind the function in the constructor:

class TextField extends React.Component {
    constructor(props) {
        super(props);

        // This binding is necessary so that the component that is rendered has access to `this`
        // as the current class
        this.myKeyPressFunction = this.myKeyPressFunction(this);
    }

    myKeyPressFunction() {
        console.log('key was pressed')
    }

    render() {
        return <input type="text" onKeyPress={this.myKeyPressFunction} />;
    }
}

Another alternative is to use the arrow function syntax that we’ve seen previously in the course. Arrow functions are smarter about this type of context and will bind this automatically. So something like this will also work without having to specify the binding in the constructor:

class TextField extends React.Component {
    myKeyPressFunction(e) {
        console.log('key was pressed', e.which);
    }

    render() {
        return <input type="text" onKeyPress={(e) => this.myKeyPressFunction(e)} />;
    }
}

Notice that by using the arrow function, we can also pass the event object (e above) and get useful information like which key was pressed that way.

More information on handling events in React can be found in the docs here: Handling Events

Passing State Down to Child Components

Part of the magic of React is that state at higher levels of the component hierarchy (parent components) can be passed down to child components through props and the child components’ props will update automatically as the parent state changes! This way, we only need to store data in one place and all the necessary updates to data can trickle down to many places where it is used.

Here’s some example code that demonstrates this:

class TextField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      textValue: ''
    };
  }
  
  handleTextEntry(e) {
    this.setState({
      textValue: e.target.value
    });
  }
  
  render() {
    return (
      <div>
        <div>
          <input type="text" val={this.state.textValue} onChange={(e) => this.handleTextEntry(e)}/>
        </div>
        <div>
          <TextDisplay key='1' textValue={this.state.textValue} />
          <TextDisplay key='2' textValue={this.state.textValue} />
        </div>
      </div>
    );
  }
}

class TextDisplay extends React.Component {
  render() {
    return <div>{this.props.textValue}</div>;
  }
}
      
ReactDOM.render(<TextField />, document.getElementById('root'));

Here you can see that the TextField component’s textValue is automatically updated in each of it’s child TextDisplay components when the text in the text input is changed.

See this interactive CodePen of the code for a demo: CodePen

Lifting State Up to Parent Components

Data can also travel the opposite direction, from a child component to a parent component, but it is a little more complicated. In this case, what essentially needs to happen is that a parent passes it’s own event callback to the child as a prop. Here’s a quick example where data travels the opposite direction as the last example we saw:

class TextWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      textValue: ''
    }
    
    // Bind the setText method to this
    this.setText = this.setText.bind(this);
  }
  
  setText(text) {
    this.setState({ textValue: text });
  }
  
  render() {
    return (
      <div>
        <div>Text value is: {this.state.textValue}</div>
        <TextField setText={this.setText} />
      </div>
    );
  }
}

class TextField extends React.Component {
  handleTextEntry(e) {
    this.props.setText(e.target.value);
  }
  
  render() {
    return (
      <div>
        <input type="text" onChange={(e) => this.handleTextEntry(e)}/>
      </div>
    );
  }
}
      
ReactDOM.render(<TextWrapper />, document.getElementById('root'));

This is what happens in the example above:

  1. The parent component TextWrapper initializes a state object with textValue and has a function setText() that can be used to update textValue in its state.

  2. In the constructor, setText() is bound to this, so that it can be linked to the setText function when being used in the template.

  3. The child component, TextField, which contains the input where we can type in text, gets passed the function this.setText as a prop.

  4. When the text input value in the child TextField component changes, the handleTextEntry function calls the setText function that was passed down by the parent component TextWrapper, to update the textValue value in TextWrapper.

CodePen with the interactive example can be found here: CodePen