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
:
identity is stable and won’t change on re-renders
mean ?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 ?
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.
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.
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.
Every state change in a hook, whether it affects its return value or not, will cause the “host” component to re-render.
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.
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>
</>
);
}
It means during different component renders useState will be the same (Object.is)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With