React Lifting State Up

Lizzie Hard
3 min readSep 17, 2021

--

This might have been a term you’ve heard about before but what do actually we mean when we talk about “Lifting state up”. Simply put it means taking the state in a child component and putting it up one or more levels on the component tree.

Astronaut lifted into space by balloons

By lifting the state from the one child component into the parent component, all 3 components now have access to the state. The parent can access it directly and pass it to all of its children via props.

A diagram of 3 nodes, one parent and 2 children. Showing state being lifted from child to parent.

Take an example of a checkbox, it can have it’s own component state where when you check it you see the UI change and the box as checked. In this example when it is checked the label becomes bold. None of the different checkboxes knows the state of the other ones and they are completely separate.

const Checkbox = ({ name }) => {
const [isChecked, setIsChecked] = useState(false);
const handleChange = () => setIsChecked(!isChecked);
const labelClass = classnames("label", { bold: isChecked });
return (
<label className={labelClass}>
{name}
<input
checked={isChecked}
className="checkbox"
type="checkbox"
name={name}
onChange={handleChange}
/>
</label>
);
};

The full code example can be found in this codesandbox:

So now you might want a button which may want to know how many checkboxes have been selected. You could do this in 2 ways:

  1. You could get rid of the checkbox component and create a new form component that has all the separate checkbox inputs with their separate state within it.
  2. A better way to go about this problem is by creating a form component with the checkbox components in it and lift the state up to the form so the form button can be passed the state as well.
const FilterForm = () => {
const [isChecked, setIsChecked] = useState([false, false, false]);
const handleChange = (itemPosition) => {
const updatedCheckedState = isChecked.map((item, index) =>
itemPosition === index ? !item : item
);
setIsChecked(updatedCheckedState);
};
const filteredArray = isChecked.filter((item) => !!item);
const filterCount = filteredArray.length;
const value = `Add ${filterCount} filters`;

const onSubmit = (e) => {
e.preventDefault();
};
return (
<form onSubmit={(e) => onSubmit(e)}>
<div className="checkboxContainer">
{colours.map((colour, index) => (
<Checkbox
key={colour}
name={colour}
checked={isChecked[index]}
handleChange={() => handleChange(index)}
/>
))}
</div>
<input type="submit" value={value} />
</form>
);
};

This looks good, we have a form now that can access the state of all its checkboxes. However maybe we want a bar which stores the filters that were clicked on the form. How can that separate component that isn’t in the form know about the checked states?

Essentially you could keep lifting up the state as to the highest common parent and then use props to pass it down. However I wouldn’t recommend this, it’s best to keep the state as close as possible to where it is being used. Passing state down via props is called prop drilling and can be useful as we’ve seen in this example but it does have it’s disadvantages as well.

  • Harder to maintain code, your code can become quite fragmented if you have props being passed through multiple components, if you add a new prop it can be easier to miss adding it in all the places.
  • Removing components means some props might be left in unnecessarily
  • Renaming props can be an issue, are you sure you remember all the places it was originally and even if you do, does the next developer who comes along to edit the code know. This is an easy way to introduce bugs.

For a simple application this may be the easiest way to go. It may be worth considering a better state management solution at some point. This is useful if a lot of different components in your app end up needing some state from a component. If you ever find yourself lifting state up to the top level then you should look at some other solutions! If you don’t want the heavy lifting of Redux then you could use the useReducer hook which I have previously written about here or something lightweight like Zustand.

--

--

Lizzie Hard
Lizzie Hard

No responses yet