Home > Software design >  React concurrent set state of parent
React concurrent set state of parent

Time:08-05

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

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

actual output is

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

CodePudding user response:

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.

  • Related