Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Hooks: setState functionality on re-renders

This is a question regarding a possible performance hit while using hooks. Quoting useState example from react docs:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

I have two queries regarding the usage of useState :

  • What does identity is stable and won’t change on re-renders mean ?
  • I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?

Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers ?

like image 540
Easwar Avatar asked Jun 13 '19 14:06

Easwar


People also ask

Does setState cause re-render?

For new React developers, it is common to think that setState will trigger a re-render. But this is not always the case. If the value doesn't change, React will not trigger a re-render.

How do you Rerender a component in React when state changes hooks?

Forcing a Re-rendering of a React Component And we create the useForceUpdate hook that updates a state with the setTick function. setTick is called in the update function which we return so we can use it in our component code. In App , we call useForceUpdate to and assigned the returned value to the update variable.

How does React hooks re-renders a function component?

As we already saw before, React re-renders a component when you call the setState function to change the state (or the provided function from the useState hook in function components). As a result, the child components only update when the parent component's state changes with one of those functions.

Do hooks cause Rerender?

Every state change in a hook, whether it affects its return value or not, will cause the “host” component to re-render.


2 Answers

What does identity is stable and won’t change on re-renders mean ?

The function returned by useState will not change across render cycles. That is, the dispatch function returned on the first render cycle can still be called after say, the 10th render cycle to set state. This allows you to setup a hook using the dispatch function at any point without needing to refresh the hook when the function reference changes.

  useEffect(() => {
    setCount(initialCount);
  }, [ initialCount, setCount ]); // <--- setCount is not needed here

I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?

Yes it is true that the arrow functions will be rebuilt on re-render. The alternative is to use useCallback to memoize a callback, but at what cost? The cost of invoking useCallback, the cost of memoizing that callback, the cost of building references to it and the cost of retrieving that callback on every re-render heavily outweighs the benefits of simply building a function on every re-render.

The useCallback function itself is 20 lines long with 3 other nested function calls to other internal React APIs. All that to prevent making a single line function on every render? The math simply does not add up in favour of useCallback. The only useful scenario is when you want your callbacks to have a "stable identity" either by reference or some other mechanism, so that you can pass the callback as props without causing excessive re-renders.

like image 127
Avin Kavish Avatar answered Oct 07 '22 13:10

Avin Kavish


Consider this component as an illustration for question 1.

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  // after each render we record the value of setCount
  const ref = useRef(null);
  useEffect(() => {
    ref.current = setCount;
  }, [setCount]);

  return (
    <>
      <div>
        Did setCount change from since render?{" "}
        {(!Object.is(ref.current, setCount)).toString()}
      </div>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}
  1. It means during different component renders useState will be the same (Object.is)

  2. Yes anonymous functions will be re-created on every render

Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers?

In this particular case, no, because useCallback does not come for free while buttons will be rendered anyway. But when we have a very heavy component to render useCallback will prevent it from unnecessary re-renders

like image 20
Oleksandr Nechai Avatar answered Oct 07 '22 12:10

Oleksandr Nechai