8

Consider the canonical useState example:

import React, { useState } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);
  console.log(count);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
      count: {count}
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default MyComponent;

Clicking the button makes each state print twice. Why is that?

screenshort

galah92
  • 3,621
  • 2
  • 29
  • 55

3 Answers3

15

Put the console.log in an useEffect hook without dependencies and you'll see it isn't actually rendering twice.

import React, { useEffect, useState } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(count);
  });
  
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
      count: {count}
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default MyComponent;

Edit wonderful-tesla-cf8uu

Here's a good diagram of the component lifecycle, it lists the class-based lifecycle functions, but the render/commit phases are the same.

enter image description here

The import thing to note is that the component can be "rendered" without actually being committed (i.e. the conventional render you see to the screen). The console.log alone is part of that. The effects run after in the "commit" phase.

useEffect

... The function passed to useEffect will run after the render is committed to the screen. ...

By default, effects run after every completed render, ...

React Strict Mode

Detecting Unexpected Side-effects

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

This only applies to development mode.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks. According to that diagram, logging inside the component body is meaningless because the "Render phase" can be restarted by React anytime, whereas `useEffect` is ensured to be called at the "Commit phase" (actual DOM render) and all logging should be done there. Am I correct to state this? – galah92 Apr 06 '20 at 05:50
  • Yes. And just for some quick dirty logging (like debugging or something) dropping a log in the functional body doesn't cause any harm really. – Drew Reese Apr 06 '20 at 05:52
3

Some more info on double re-rending (as per a closed react issue).

It's an intentional feature of the StrictMode. This only happens in development, and helps find accidental side effects put into the render phase. We only do this for components with Hooks because those are more likely to accidentally have side effects in the wrong place.

https://github.com/facebook/react/issues/15074

another related question is here.

useState() do double render

gdh
  • 13,114
  • 2
  • 16
  • 28
0
const countRef = useRef(1);
countRef.current += 1;

This way render will execute 2 times at a time


const countRef = useRef(1);
useEffect(() => {
   countRef.current += 1;
})

This way render will execute 1 times at a time

Clearives
  • 1
  • 1