Lifting state in React passes the state from a child component to its parent. The parent is the central controller for state. Once the state is stored in the parent, it can be passed to other child components, including the original child. In this article, we will explore passing it to a different child.
This is a very simple example to show why you may want to lift state up: You have a website that allows a user to click a brush icon to draw on a canvas. You have three components: App.js (the parent component), Brush.js (child), and Canvas.js (child).
The user will click the brush icon in Brush.js to enable and disable the drawing tool. However, the drawing tool lives in the Canvas component. To communicate to the canvas if it’s enabled or disabled, the state will be passed from the brush → main app → canvas.
All code used here is in this Code Sandbox: https://codesandbox.io/s/lift-state-up-o9n2l?file=/src/App.js
Let’s begin lifting state with our brush child component:
1. Add a function to a child component that handles an event listener.
Our first child component is the brush. The brush is a button with an event listener.
Here, we’ve added an onClick event listener to the brush that calls the onBrushClick function when triggered. The function is called above.
The problem here is that we can’t do what we want—activate the canvas—from the brush component. Even if we introduced state here, we won’t be able to communicate with the canvas. We have to add state to the parent and pass it the status of the brush.
2. Add state to the parent component.
We’ve moved into the main App.js file, because it is our parent component. (Your parent component may be a different file.)
Because we want our parent to control data, we will initialize state here:
{ draw: false } is our initial state. My state variable is ‘draw’, which I picked because my state is controlling a canvas element, and its initial state is set to ‘false’, because I want the canvas to be disabled when the user visits the site. I also have a constructor with (props) here, because we will be passing the onBrushClick function to the brush as a prop. I also set my initial state.
So, we have our initial state, but we need the function that will eventually change the state. Let’s transfer our function from the child component to the parent component. Back in the brush child component:
Two changes here: 1. the function is removed, and 2. the event listener calls this.props.onBrushClick, instead of this.onBrushClick.
Now we add the same function and bind it in the parent component, App.js:
Our function is in the parent component. However, these components are not communicating: the function isn’t called. These two components now need a way to communicate. This works through passing props.
We already updated the function in our brush component to be this.props.OnClick instead of this.onClick. Now we just need to pass the function onClick as a prop to the brush component.
Now we are getting into the return() method of our App component. Before, I used the placeholder /*Return goes here*/. Here is the actual code that in the return method:
We’re passing the onBrushClick function as a prop, so that it will be called on this.props.onBrushClick.
<! — Explanation about props — ->
(skip if you prefer, you can finish without knowing how props work)
Props are named like variables. The onBrushClick={this.onBrushClick} prop shown here could have been named startPainting={this.onBrushClick} and it would have been completely fine. The only difference is, I would have to call this.props.startPainting from my brush component.
Props only flow from parents to children. To show what we’re passing, we can console.log props from the app component and see what appears:
… It’s an empty object! That’s because props are never passed from child to parent — always from parent to child. This really confused me at first. We call the function in the parent, but props are only passed to children.
If props are console.logged in the child, we get this:
Here is the record of our function, onBrushClick, in the child component. This makes sense because props are only passed to children.
3. Change state in the parent from its child component.
In our parent component, we are now ready to set the state we initialized, using the built-in setState function.
The onBrushClick() function here changes the state to its Boolean opposite when it is called. Ex., if the initial state is true, it will be changed to false and vice versa. Another way to change state would be:
this.setState({ draw: !this.state.draw });
However, it is a best practice to use the built-in prevState argument when changing state based on previous state.
This toggles our state off/on when the brush is clicked. We have now fulfilled the first aim of this article, lifting state up.
4. Use the parent component as a central controller for state.
We want our parent component to control state, so we can pass it to a different child. We will do this now by introducing our second child component, the canvas. Here is our entire App.js component code with two children:
I included an entire code block to show everything so far. The new important piece here is the addition of the canvas component, our second child. We pass the canvas the state as a prop (again, ‘draw’ here could be named ‘enableCanvas’, ‘paint’, or ‘goPicassoMode’).
5. Acting on state in a second child component.
The final step! Let’s go into the child component we passed state to, the canvas.
In my canvas component, I’m using the built-in HTML element, <canvas> with React. If you’re interested in using <canvas> in React, take a look at this tutorial; I’m going to omit all of my canvas-specific code here.
Here is the Canvas component. We’ve passed the state as props into this component, but have not acted on them yet.
If we call console.log(draw) in the render method we get the entire state object. We have the key and value pair::
However, we’re only really interested in the value, because we want to know if it’s true or false. In JavaScript, there are few ways to extract the value from an object, and we could use the Object.value method. But it is easier to use dot notation with the props object: props.draw will return true or false. props.draw will dependably tell us if the state in our parent component is true or false.
Now we can act on it in the canvas component. I want to use state to change the color of the canvas depending on if the state is true or false.
I used state to write a conditional statement. Depending on the condition, the background color will be white or gray.
The ternary operator changes the color of the canvas based on the state. If the state is equal to true, the canvas color is white. If it’s false, it is gray. The important thing is, we changed a child component based on the state we passed from our parent and lifted from another child component.
Here is a CodeSandbox demo again: https://codesandbox.io/s/lift-state-up-o9n2l?file=/src/App.js
React documentation on lifting state.
This is an overview of what was accomplished, using our initial diagram: