Search code examples
reactjsreact-nativereact-hooksrace-condition

Best way to synchronize state variables


I'm new to react native and am trying to create onboarding for my app, this is a simplified version. To stop the user from half answering my onboarding questions and allowing them to mess up the user data, I have a tempUserData that the changes are made to which will finally be committed to the userData once they finish a section.

The problem is that when I call setTempUserData (last question for a section answered) and then call commitUserData to commit the data, an old version of tempUserData is committed to the UserData.

My thought is that this may have to do with a sort of race condition, but I can't figure out the best way to go about fixing this issue and maybe somehow synchronizing the updates, or if my strategy of doing onboarding isn't ideal. Thanks for helping.

const [tempUserData, setTempUserData] = useState({ name: '', age: '' });
  const [userData, setUserData] = useState({ name: '', age: '' });

  // Function to update temp user data
  const updateTempUserData = (newData) => {
    setTempUserData((prevTempUserData) => ({
      ...prevTempUserData,
      ...newData,
    }));
  };

  // Function to commit temp user data to actual user data
  const commitUserData = () => {
    setUserData((prevUserData) => ({
      ...prevUserData,
      ...tempUserData,
    }));
  };

I want the userData to reflect the last state of the tempUserData but I can't figure out if this is a natural way of using react or if there's a better way of going about this.


Solution

  • The change to tempUserData is only visible when the GUI re-renders. You can only ever see state updates when Reactjs re-renders the GUI and it usually batches this GUI re-render and doesn’t do this on the fly for performance reasons so in the meantime, all you will see is the stale value of tempUserData until the next re-render occurs. [1]

    You are better off keeping a separate simple Javascript object that you will use for other computation while you leave state alone for only GUI rendering. The only problem is that state will persist through multiple GUI re-renders while the Javascript object won’t be preserved each time a GUI re-render occurs.

    The better alternative is to use references to values. References that will persist through out GUI re-renders. These references will be preserved. I’m talking about useRef but of course Reactjs does not track mutations to such references so if you want to keep displaying state updates to users, you certainly have to use state that Reactjs will track and will re-render the GUI with when they change so that the change can be reflected in the GUI.

    Here is one solution:

    const data = { name: '', age: '' }
    const [tempUserData, setTempUserData] = useState(data)
    const { current: tempUserDataRef } = useRef(data)
    const [userData, setUserData] = useState(data)
    
    const updateTempUserData = (newData) => {
      setTempUserData(prevTempUserData => ({
        ...prevTempUserData,
        ...newData
      }))
      tempUserDataRef = {
        ...tempUserDataRef,
        ...newData
      }
    }
    
    const commitUserData = () => {
      setUserData(prevUserData => ({
        ...prevUserData,
        ...tempUserDataRef
      }))
    }

    [1] Reactjs does more than re-render the GUI.