42
    const [active, setActive] = useState(false);

    const onActiveChanged = useCallback(
      isActive => () => {
        // do something
        setActive(isActive);
      },
      [setActive], // or just [] is okay?
    );

When using useState and useCallback (or useMemo) together, should I include setState in the array of dependencies?

alanhchoi
  • 1,160
  • 2
  • 11
  • 17
  • @AngelSalazar The updater method from useState isn't memoized or doesn't persist the same reference? – alanhchoi Apr 18 '19 at 03:10
  • 5
    my bad, according to the docs "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." – AngelSalazar Apr 18 '19 at 03:17
  • @AngelSalazar Thanks for confirmation! I also found that part in https://github.com/reactjs/reactjs.org/blob/master/content/docs/hooks-reference.md So just passing an empty array must be okay. You can leave it as an answer to this question or I'll post an answer to my own question. – alanhchoi Apr 18 '19 at 03:23

2 Answers2

61

The recommendation for that is also on React Docs - Hooks API Reference.

The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.

setState(newState);

During subsequent re-renders, the first value returned by useState will always be the most recent state after applying updates.

Note

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.

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
2

The purpose of useCallback as you rightly hinted is to memoise:

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

And as for what useMemo is intended for:

You may rely on useMemo as a performance optimization, not as a semantic guarantee.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

But as a rule, useState is stable between renders (i.e. pre-memoised), so you should not need to memoise it again, so it can be safely omited from dependency callbacks.

The question then comes, is your 'do something' below an expensive calculation? It shouldn't be to onerous to make use of useCallback, but it could well be boilerplate code you don't need, and could make almost direct use of your setActive function.

const [active, setActive] = useState(false);

const onActiveChanged = useCallback(
  isActive => () => {
    // do something
    setActive(isActive);
  },
  [],
);

Another way to prevent unnecessary dependencies, in your useCallback and other hooks, is to make use of functional updates. The result being that you can have these:

const [active, setActive] = useState(false);
const [expensiveCalc, setExpensiveCalc] = useState(false);

const onExpensiveCalc = useCallback(
  expensiveInput => () => {
    const newState = doExpensiveCalc(expensiveInput);
    expensiveCalc(newState);
  },
  [setActive], // here for completeness only
);

return (<>
  // expensive calculation
  <button onClick={onExpensiveCalc}>Do lengthy calculation</button>
  // cheap calculation, using functional updates
  <button onClick={() => setActive(prevBoolean => !prevBoolean)}>Cheap Set Active</button>
</>)


Do note, that there's a little nuance to how set state works in an onClick, and you should make use of an arrow function, so your setActive is run on click, not render. This is shown in the second answer above, but without explanation.

See also: What is useState() in React?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173