Reputation: 1436
I have two components, one placed inside the other. The children component prints the state passed by the parent and updates the state once with useEffect. In the parent component I'm using setState (an object with boolean values) and passing the state to all the children.
Parent component:
export default function App() {
const [state, setState] = useState({
one: false,
two: false,
three: false
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<ChildComponent
value={state.one}
setState={(val) => setState({ ...state, one: val })}
/>
<ChildComponent
value={state.two}
setState={(val) => setState({ ...state, two: val })}
/>
<ChildComponent
value={state.three}
setState={(val) => setState({ ...state, three: val })}
/>
</div>
);
}
Child Component:
import { useEffect } from "react";
export default function ChildComponent({ value, setState }) {
useEffect(() => {
console.log("updated");
setState(!value);
}, []);
return (
<>
<p>{value ? "true" : "false"}</p>
</>
);
}
Both components are really simple, and I'm expecting to see all boolean values being true (because the child components are modifying the state). The problem is that only the last item on the state is being updated. Can someone explain what is wrong with my code? A working example
Upvotes: 3
Views: 1750
Reputation: 127
see what is happening here is, when you are sending the state in the first child component, the state.one is updated to true , but then, you move on to the second component, that is using the same previous state where state.one was false and the same happens in the third case too.
so what you need to do is pass a callback of the new updated state to the function setState.
setState= {(val)=>{setState(currState => ({ ...currState, two: val })}}
Upvotes: 0
Reputation: 5284
Edit: you can handle it as @koralarts suggested, but if you want to understand why it happened,read this.
let's explore what's happens in your example.
TLDR: you have to split the setState call to separate hook in the parent for each child component, or manage the state in the children if possible.
each time you calling setState in the effect react schedules this call, and not execute it directly.
you are calling 3 times the same setState function in the same render, and each time react schedules the call to the next render, therefore, overriding the previous schedule.
If possible, manage this state from the children, if not, use different useState for each child on the parent.
let's be aware of the different lifecycle of a react component:
you can read the complete explanation here(highly recommended).
see this sandbox(like yours with logs).
read the difference between 'render' and 'update' here:
render - React tree is created based on the current state, then the tree is passed to the renderer that will update the VDOM, and changes will be flushed into the browser's DOM. render cycle consists of few phases that will be explained later. when we say that a component ' render' we are usually talking about the end of the render cycle, after changes are flushed into the browser's DOM.
update - when we say that a component 'updates', we are saying that the function component body re-executed (with possibly different props). is one of the phases of a render. multiple update cycles are possible during a render cycle. examples of the difference between update and render later.
now read this important notes:
Calling state hook from effect(like useEffect or useLayoutEffect) will cause React to schedule another render.
Calling state hook from FC body will cause React to schedule another update call.
Upvotes: 2
Reputation: 4480
I think is because asynchronous behaviour of setState
, By the time it renders the ChildComponent
, it still having the old state
not the updated one. Like @koralarts answer, you have to use previous state to update the state.
Upvotes: 0
Reputation: 2112
The state update is dependent on the previous value of the state, you need to pass a callback as the argument for your setState
instead.
You need to do this instead:
setState={(val) => setState(prev => ({ ...prev, one: val }))}
In your current implementation, you're spreading the initial value of the state where everything is false
instead of the current value of the state.
Example: https://codesandbox.io/s/usestate-callback-1oq2n?file=/src/App.js
Upvotes: 2