Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: why is the state returned from useState stays the same when the initializer function gets called on every re-render?

Here is the live demo: https://codesandbox.io/s/strange-bash-qj7ld?file=/src/App.js

So I have a component like this

const fn = () => {
  console.log("useState");
  return ["string"];
};

export default function App() {
  const [counter, setCounter] = useState(0);

  const [nonprimitive, setNonprimitive] = useState(fn());

  useEffect(() => {
    console.log("useEffect");
  }, [nonprimitive]);

  return (
    <>
      <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button>
      <div>{nonprimitive[0]}</div>
    </>
  );
}

every time I clicked on the button, the counter will increase and the component will re-render. and then fn() will be called again, then it will return an array. and the useEffect is triggered when nonprimitive is changing. I have two questions:

  1. why is that useEffect gets triggered only once, not for every subsequent calls? nonprimitive is an array, which is a non primitive type, so it should be different every time even when the string item inside of it stays the same. It seems like React is doing some deep comparison under the hood.
  2. Why is the useState gets printed out twice whenever the component re-renders? i.e. console.log("useState"); inside fn gets called twice when I hit the button.
like image 494
Joji Avatar asked Oct 23 '25 18:10

Joji


1 Answers

why is that useEffect gets triggered only once, not for every subsequent calls? nonprimitive is an array, which is a non primitive type, so it should be different every time even when the string item inside of it stays the same. It seems like React is doing some deep comparison under the hood.

The whole point of useState is to persist values across renders of your component. The first time your component renders, useState(fn()) is called and nonprimitive gets the value ["string"]. On subsequent renders, useState(fn()) is called again (because, after all, it is still a standard function call, and so the logging happens again), but React knows that it's not your first render, so it throws away the argument (the return value of fn) and instead returns the previously stored value, which is referentially the same.

If you don't want React to call your initial state creation on every render (for example, if your initial state is very complex or time-consuming to calculate), you can pass it a function which it will only call on the initial render.

const [nonprimitive, setNonprimitive] = useState(() => fn());

In your case, since the initial state is already wrapped in a function, you can equivalently write

const [nonprimitive, setNonprimitive] = useState(fn);

Why is the useState gets printed out twice whenever the component re-renders? i.e. console.log("useState"); inside fn gets called twice when I hit the button.

If you look in your index.js, you'll notice that your App component is wrapped in <React.StrictMode>. React strict mode will intentionally run certain renders more than once to help you catch side effects and hook violations in your code.

like image 101
Elan Hamburger Avatar answered Oct 26 '25 07:10

Elan Hamburger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!