1

I am facing some very strange behaviour with useState React hook. In the following code (https://codesandbox.io/s/purple-bush-nb5uy?file=/src/index.js):

function App() {
  return (
    <div className="App">
      <Comp flag={true} />
    </div>
  );
}
const Comp = ({ flag }) => {
  const [running, setRunning] = useState(false);
  const [jumping, setJumping] = useState(false);
  console.log('zero');
  
  const setBoth = () => {
    setRunning(true);
    console.log('one');
    setJumping(true);
    console.log('two');
  };

  return (
    <>
      {"running: " + running}
      {"jumping: " + jumping}
      <button onClick={() => setBoth()}>setboth</button>
    </>
  );
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

when we click the button, we get the following sequence into the console:

one
two
zero

I would expect:

zero
one
zero
two

since I think that React re-renders immediately should it find a useState setter and the following code executes after re-render. Moreover, that's the case with my React application:

const [time, setTime] = useState('');
console.log('Hey');
const updateTime = (e) => {
        setTime(e.details);
        console.log('Hello');
    };

    useEffect(() => {
        window.addEventListener("updateTime", updateTime);            
        return () => {
            window.removeEventListener("updateTime", updateTime);
        }
    }, []);

What happens with the above code when updateTime runs and when e.details value is different than state variable time's content:

Hey
Hello

In other words, the re-rendering runs first and the code after the setter runs afterwards. So, why do we have different behaviour in the above cases? What is the explanation and what happens under the hood?

Unknown developer
  • 5,414
  • 13
  • 52
  • 100
  • Not sure about your problem, but useState are asynchronous https://stackoverflow.com/questions/54119678/is-usestate-synchronous – dbuchet Jan 08 '21 at 17:20
  • Please provide the relevant code snippets (for each example your describing) inside the question description. Off-site resources can be used to add context but shouldn't be needed to understand the question. – Emile Bergeron Jan 08 '21 at 17:21

3 Answers3

5

-First question

According to Dan Abramov (Co-creator: Redux, Create React App.)

Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event

and in your first case click event is a react event

-Second question

According to Dan Abramov

However, both in React 16 and earlier versions, there is yet no batching by default outside of React event handlers. So if in your example we had an AJAX response handler instead of handleClick, each setState() would be processed immediately as it happens. In this case, yes, you would see an intermediate state:

window.addEventListener is not a react event, so it should be rendered immediately.

You can found the full anwser of Dan Abramov here

I made an example here containing the two scenarios

ARZMI Imad
  • 950
  • 5
  • 8
1

It was expected behaviour, react usually re-render the component only when the state or props changed. React's useState is an asynchronous function, that is the reason for your console.log order miss-match.

You can use useEffect to listen to the changes and fire the function as you need.

Codesandbox link


const Comp = ({ flag }) => {
  const [running, setRunning] = useState(false);
  const [jumping, setJumping] = useState(false);
  const setZero = () => {
    console.log("zero");
  };

  useEffect(() => {
    setJumping(!running);
    console.log("two");
  }, [running]);

  const setBoth = () => {
    setZero();
    setRunning(!running);
    console.log("one");
  };

  return (
    <>
      <pre>{JSON.stringify({ running, jumping })}</pre>
      <button onClick={() => setBoth()}>setboth</button>
    </>
  );
};
Naveen DA
  • 4,148
  • 4
  • 38
  • 57
-1
 const setBoth = () => {

    setRunning(true);
    setJumping(true);

 };

The above code is known as batching. This means that react, behind the scene updates by calling setRunning and setJumping at the same time. This results in a single re-render.

Therefore, when you click on the button which changes both the state, both the states are set at once. Therefore we print

// "1"

// "2"

// "3" =======> after rendering again.

UseEffect is a react hook which is called after re-rendering.

Therefore, first the "Hey" is printed =====> componenent has been Rendered

Next, useEffect is being called because useEffect is called by react after re-rendering.

Hence it prints "Hello".

Hrushikesh Rao
  • 109
  • 1
  • 4