Example: https://codesandbox.io/s/react-hooks-playground-forked-15ctx?file=/src/index.tsx
One parent with one useState hook and 3 children with a useEffect hooks. I want the children to update the state in the parent via a callback so that all the components state are updated into one object in the parent. This does not work for the initial/first render as the current state does not update between each useEffect triggered callback (see example above, or code below). This means the combined state consists of only the state from the last child.
const ChildElement = ({ index, callback }) => { useEffect(() => { callback(index); }, []); return <>{index}</>; }; const App = () => { const [state, setState] = useState(0); const callback = (index: any) => { const newObject = { a: index }; setState({ ...state, [index]: newObject }); }; console.log(state); return ( <> <ChildElement index={0} callback={callback} /> <ChildElement index={1} callback={callback} /> <ChildElement index={2} callback={callback} /> </> ); };
- I could declare the initial state in the parent, but that’s more code
- I could use in render cache (an object I manually update like useStae, but changed immediately), but that feels dirty
- Is the hook useReducer a good solution here?
Any suggestions? What’s the best approach to solve this problem?
Advertisement
Answer
You are facing a race conditions.
if you change
const callback = (index: any) => { const newObject = { a: index }; setState({ ...state, [index]: newObject }); };
to:
const callback = (index: any) => { const newObject = { a: index }; setState((currentState) => ({ ...currentState, [index]: newObject })); };
your code will work
although this:
all the components state are updated into one object in the parent
is not a good practice.
its better child components to have their own state and you can reduce code by creating a custom hook if those state act like each other.