🔥 Hot React Tips: useState Accepts a Function Argument

If you’re using React’s useState hooks in your frontend application, you may not know that the setter function can also accept a function as an argument. This can be used to avoid unnecessary re-renders when combined with useCallback.

Consider the following example:

const MyCounter = () => {
  const [counterValue, setCounterValue] = useState(0);

  const incrementCounter = useCallback(() => {
    setCounterValue(counterValue + 1);
  }, [counterValue]);

  return (
    <>
      <Text>{counterValue}</Text>
      <Button onClick={incrementCounter} title="Increment Counter" />
    </>
  );
};

Everything looks great. We have a memoised callback which will increment the counterValue variable. However, it’s actually not that great: if you look closely, this memoised callback is going to get a new identity each time that counterValue changes.

A more efficient way to represent could be as follows:

const MyCounter = () => {
  const [counterValue, setCounterValue] = useState(0);

  const incrementCounter = useCallback(() => {
    setCounterValue((previousCounterValue) => previousCounterValue + 1);
  }, []);

  return (
    <>
      <Text>{counterValue}</Text>
      <Button onClick={incrementCounter} title="Increment Counter" />
    </>
  );
};

Because the setter returned by useState also accepts a function argument which will be invoked with the previous state value, we can simply use the previous value to compute the new counter value. This also avoids changing any dependencies of useCallback and so the identity of the callback never changes.

Summary

  • Use the functional argument of the useState setter to access the previous state value
  • Avoid identity changes caused by inefficient usage of useCallback which can cause unnecessary re-renders