Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React concurrent set state of parent

suppose that you want some child component to set the state of a parent component at init (not on user input). In the example that follows, if you have more than one component changing the same parent, changes made by some of the children are lost!

What is the right way to implement concurrent changes to parent?

import { useEffect, useState } from 'react';


function Child({letter,parentState,setParentState}) {

  const [value,setValue] = useState("hello");

  useEffect(()=>{
      const new_parent_state={
        ...parentState,
        [letter]: "hello"
      }
      setParentState(new_parent_state);
  },[value]);

  return <div>hello {letter}</div>

}


function App() {

  const [parentState,setParentState] = useState({"A": null, "B": null})

  useEffect(()=>{
    console.log(parentState);
  });

  return (
    <div className="App">
      <Child key="A" letter="A" parentState={parentState} setParentState={setParentState}></Child>
      <Child key="B" letter="B" parentState={parentState} setParentState={setParentState}></Child>
    </div>
  );
}

export default App;

expected output is

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

{
    "A":"hello",
    "B": "hello"
}

actual output is

{
    "A": null,
    "B": "hello"
}

>Solution :

Component A:

parentState = {"A": null, "B": null}.
A updates the state to: {"A": "hello", "B": null}

Component B:

parentState = {"A": null, "B": null}.
B updates the state to: {"A": null, "B": "hello"}

If you swap the order of A and B components in the DOM tree you will see the other output.

The reason for the issue you see is parentState provided to a single component is stale. You need to use the setter callback for it to work (get updated state always) as you expect.

Instead of:

const new_parent_state={
  ...parentState,
  [letter]: "hello"
}
setParentState(new_parent_state);

use:

setParentState((prevParentState) => ({
  ...prevParentState,
  [letter]: "hello"
}));

Working Demo:

function Child({ letter, parentState, setParentState }) {
  const [value, setValue] = React.useState("hello");

  React.useEffect(() => {
    setParentState((prevParentState) => ({
      ...prevParentState,
      [letter]: "hello"
    }));
  }, [value]);

  return <div>hello {letter}</div>;
}

function App() {
  const [parentState, setParentState] = React.useState({ A: null, B: null });

  React.useEffect(() => {
    console.log(parentState);
  });

  return (
    <div className="App">
      <Child
        key="A"
        letter="A"
        parentState={parentState}
        setParentState={setParentState}
      ></Child>
      <Child
        key="B"
        letter="B"
        parentState={parentState}
        setParentState={setParentState}
      ></Child>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

SIDE NOTE: You may have expected three logs here. But React has intelligently batched the two state updates to a single update.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading