Deepak Singh
Deepak Singh

Reputation: 650

Update parent state from multiple child components at same time

I have a parent component that has a state of its own and that is shared across the child components as well.

In my child component, I have a form and for the data, I've created a local state. Let's call this child component .

In my parent component, there is a button on click of which I update the data from my child's local state to my parent's state. This I do by passing a flag from parent to child.

Also my parent renders the same child twice making the final code like this:

const parent = () => {
  const [parentState, setParentState] = useState(null);
  const [submitSignal, setSubmitSignal] = useState(false);

  const handleSave = () => {
    setSubmitSignal(true);
  }

  return (
    <div>
      <div onClick={handleSave}>
        Save Data
      </div>

      <Child
        pos={0}
        key={0}
        parentState={parentState}
        submitSignal={submitSignal}
        setParentState={setParentState}
      />

      <Child
        pos={1}
        key={1}
        parentState={parentState}
        submitSignal={submitSignal}
        setParentState={setParentState}
      />
    </div>
  )
}

const Child = (props) => {
  const [childState, setChildState] = useState(null);

  React.useEffect(() => {
    const { pos, submitSignal, parentState, setParentState } = props;
    if (submitSignal) {
      setParentState(...parentState, ...childState);
    }
  }, [props.submitSignal])

  return (
    <div>
      // A large form which multiple fields
    </div>
  )
}

Now what's happening here is as soon as I make the submitSignal true from my parent component both the child receive it at the same time and try to update the parent's state. It's kind of a race condition situation where Child 0 updates the data but before it's even get updated in parent state Child 1 also updates the parent's state and thereby overwriting/removing what Child 0 added.

Please help me out with this.

P.S: The reason why I have gone with this structure is that in my Child I have a very large form and the same form is used twice.

Upvotes: 5

Views: 3046

Answers (2)

Eliav Louski
Eliav Louski

Reputation: 5274

I see why you are confused, this is actually working exactly as intended.

before I continue: be aware:

  • on this section - read 'render' and 'update' definitions.
    in summery: 'update' is execution the FC(Fonctional Component) body and 'render' is update(s) including thier following effects.
  • Calling state hook from effect(like useEffect or useLayoutEffect) will cause React to schedule another render. see example
  • Calling state hook from FC body will cause React to schedule another update call. see example.

for more details, read my article how-react-hooks-work. it will help you understand better functional components lifecycle and hooks.

about your case:

about your specific issue: follow this code sandbox, it's just like your code with some logs.

order

phases order:
  • parent update
  • child update * 2
  • child effect * 2
  • parent effect
logs
  • on mount, parent updates, and then the 2 children updates. then children effects are fired, then parent effects are fired.
  • click occurred => handleSave is called which will set the state, in the FC body, so react will schedule another update.
  • the update is executed (logging 'Parent' and 'Child'*2). and submitSignal is set to true.
  • now we are on the effect phase: 'submitSignal' was updated, so both children fill setState (on Parent) inside the effects phase => another Parent render cycle is scheduled.(is does not matter 2 different children called setState, only one render will be scheduled by react).
  • Parent(and chidrens) updates with updated values.

there are no any racing conditions here, react doesn't care if 1000 children requested an update(via setstate for example) on the parent. because all the update request was on the same phase, only one render will be scheduled.

if you having a hard time understanding it, I would really recommend reading my article, how-react-hooks-work, which has simple to complicated examples that demonstrate these behaviors.

Upvotes: 0

hema sundar Ginni
hema sundar Ginni

Reputation: 323

Instead of setting the parent state directly, you have to use a callback function. this way, you will get updated previous state while overriding state.

    if (submitSignal) {
      setParentState((pstate)=>{
         return {...pstate,...childstate}
      });
    }

https://stackoverflow.com/a/68531170/5707801

Upvotes: 1

Related Questions