Home > OS >  Writing simultaneously to a single state object from multiple components
Writing simultaneously to a single state object from multiple components

Time:11-08

In my create-react-app app, I created an object using the useState hook in App.js that I pass down as an argument to 5 different instances of the same component. This object contains 5 different arrays under simple number property names (so they're easy to iterate over with a for loop).

const [ listObj, setListObj ] = useState({ 0: [], 1: [], 2: [], 3: [], 4: [] });

Each component maintains and modifys one and only one of those arrays (corresponding to it's number, so component 0 only modifies component 0, 1 only modifies 1, etc.), and I use functions to iterate over listObj and the arrays inside so I can compare their contents.

These components each have a unique componentNumber (0-4) passed to them through App.js, and a useEffect hook that looks something like this in each one for every time they want to update their specific array:

useEffect(() => {
  let newArray = functionToGenerateSortedArrays();
  let newListObj = { ...listObj, [ componentNumber ]: newArray };
  setListObj( newListObj );
}, [ (all my state objects that trigger useEffect) ])

This works great for when I'm only updating something pertaining to one component at a time. The problem is that there is a functionality in the app where all the components need to update at the same time. It appears that each one will grab an "old" copy of the listObj, use that to create a newListObj with the updated information, then call setListObj with the newListObj, thereby overwriting the changes that the other components tried to make. The only listObj array that updates properly is the last one in the row.

I thought myself terribly clever when I tried to implement this hacky solution:

useEffect(() => {
  let newArray = functionToGenerateSortedArrays();
  setTimeout(() => {
    let newListObj = { ...listObj, [ componentNumber ]: newArray };
    setListObj( newListObj );
  }, componentNumber * 100 );
}, [ (all my state objects that trigger useEffect) ])

This doesn't work, though. It actually switches the array in listObj back to the value I want instantly, but it reverts to the wrong one after the timeout triggers. I have no idea why but would love to understand it if someone has any insights. I thought perhaps it could be related to how React "batches" state updates, but couldn't find any information on how to keep React from doing that so I could test my hypothesis.

Do I have any options for changing the array values in listObj in a "cascading" manner so they don't interfere with each other? Is there perhaps another way of saving and updating state that won't create this problem? Thanks very much for your time.

CodePudding user response:

This is where to us the state update callback form of the useState hook's setter:

useEffect(() => {
  let newArray = functionToGenerateSortedArrays();
  setListObj(currentListObj => {
//           ^^^^^^^^^^^^^^
    let newListObj = { ...currentListObj, [ componentNumber ]: newArray };
    return newListObj;
  });
}, [ (all my state objects that trigger useEffect) ])

That way, even when all the update functions are batched together, they will receive the current value instead of using the outdated one that the effect function had closed over.

  • Related