React's state update vs ref async update

Viewed 155

React's state update is async and that's why you should not try to use the state (like logging or any other reading operation) just after setting it. But only async keyword seems to not give the total picture of how React updates the state.

How is this state update different from a ref update done asynchronously ?

Consider the following snippet where we simulate async updation of statefulRef. Even after we update the statefulRef asynchronously, if we access it after the update, we will get the latest value. But the same is not true with state. I know that both render and closure take part in explaining this state update but I would like to have a detailed answer explaining the differences of async behavior that we see below :-

const {
  useState,
  useRef
} = React

function App() {
  const [state, setState] = useState({current:0});
  const statefulRef = useRef(0);

  function updateState() {
    // below is async operation
    setState({current:state.current + 1});
    // below is async logging
    setTimeout(() => console.log("React state : ",state.current), 500);
  }

  function updateRef() {
    // below is async operation
    setTimeout(() => statefulRef.current += 1, 200);
    // below is async logging
    setTimeout(() => console.log("React ref : ",statefulRef.current), 500);
  }

  return ( 
    <div >
    <button onClick = {updateState}> Update State </button> 
    <button onClick = {updateRef}> Update Ref </button> 
    </div>
  )

}

ReactDOM.render( < App / > , document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

1 Answers

state is captured at the moment the program execution enters the updateState function. So the console.log will show an outdated value with or without the setTimeout.

Same is true for statefulRef, it's captured when the execution of updateRef begins. But statefulRef is an object and in JavaScript objects are references. What is captured is the reference, not the value (the values of the properties). So, even though statefulRef will not change, statefulRef.current may. Whereas with state things work more or less like this:

function updateRef() {
    const current = statefulRef.current;
    // below is async operation
    setTimeout(() => (statefulRef.current += 1), 200);
    // below is async logging
    setTimeout(() => console.log("React ref : ", current), 500);
}

UPDATE: The fact that state itself is an object (as in the updated question), doesn't change much. setState replaces the old value with a new one, it doesn't assign to state's properties without changing state itself. You could achieve something similar by doing state.current = 1 but you shouldn't because it would break React's assumptions about state management.

Related