Stop Duplicating Your React State!
If you manage the same state in multiple places, you're doing it wrong
React was first introduced to the world in May of 2013. After the horrified gasps died down over the idea of including marking inside of JavaScript functions, we were left to grapple with how a unidirectional dataflow would work in a frontend application.
I would definitely shudder to look back at some of my first attempts at writing React applications fresh off two-way binding frameworks like Angular and KnockoutJS. I don't remember when it happened, but a few months into my React journey, I had an epiphany that state in React was meant to be a tangible thing that lived at a time and place, not some nebulous event that got passed around. React was building a tree of state and components and at any give time, if I provided the same state, the application would behave in the same way. This all probably seems obvious eight years later, but at the time, it was a major mental shift for me.
The most major implication of this change of thinking is where we put local state. As a rule, it should be as low down the render chain as it can go for the components that need the state to be aware of the state.
Take this contrived example:
// Child Component
const MySelect = ({ initialValue, onValueChange }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
useEffect(() => {
onValueChange(value);
}, [value]);
return (
<select onChange={(e) => setValue(e.target.value)} value={value}>
<option>Option1</option>
<option>Option2</option>
</select>
);
};
// Parent Component
const MyForm = () => {
const [initialState] = useState("Option1");
return (
<MySelect
initialValue={initialState}
onValueChange={(value) => {
console.log("Value Change", value);
}}
/>
);
};
Do you see the problem here? The parent component (<MyForm />) needs to know what the state of the child component is. We're setting the initial state and passing it down. We also have to attach a callback to get the value when it updates.
The issue is that we are duplicating the state in two places. The form and select are both managing their own copies of the same state.
This sort of duplication of the state is actually really common in the code I've reviewed over the past several years, but it can lead to several issues. First of all, it's more complex than it needs to be.
Instead, we could let the parent manage all of the state and make the child component completely presentational. Here is an example of hoisting the responsibility of maintaining the state to the parent component:
// Child Component
const MySelect = ({ value, setValue }) => {
return (
<select onChange={(e) => setValue(e.target.value)} value={value}>
<option>Option1</option>
<option>Option2</option>
</select>
);
};
// Parent Component
const MyForm = () => {
const [value, setValue] = useState("Option1");
return (
<MySelect
value={value}
setValue={(value) => {
setValue(value);
console.log("Value Change", value);
}}
/>
);
};
Here, the <MySelect /> component just takes the value and setter from the <MyForm /> component. There are fewer lines of code since we aren't managing the state in both places, but more importantly, there is less complexity. We don't have to wonder if the state of the child component and parent component are in sync because we know that the child component is rendering the state that being passed down into it.
I once tweeted (back when I had a Twitter account) that as a rule, most useEffects could probably be elimanated by just deriving state correctly from their source. This is a classic example of that. We were able to eliminate two useEffects by just using the props passed down into our component.
I think this is worth reiterating. Always manage your state as low down the render tree as you can keep it without needing to duplicate it. You definitely want to keep it lower down the chain so you aren't rerendering the entire screen on every state change (I've made that mistake as well), but high enough that there is no reason to need it in two places.
Stop duplicating your state and make your life easier.



