Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does mapping a React hook work, but a lambda violates rule of hooks?

If I have a hook, e.g:

const useGetSet = (label: string) => {
  const [get, set] = useState(label);
  return { get, set };
};

I can map it over an array, e.g:

const labels = ['one', 'two'].map(useGetSet);

But if I expand this to a lambda, e.g:

const labels = ['one', 'two'].map((l) => useGetSet(l))

Then it causes:

React Hook "useGetSet" cannot be called inside a callback.
React Hooks must be called in a React function component or a custom React Hook function. (react-hooks/rules-of-hooks) eslint


Why this difference, shouldn't they be equivalent?

Additionally, if this is a violation of the rule of hooks, how should this be done?

Full working example here.

like image 956
davetapley Avatar asked Jan 25 '26 10:01

davetapley


2 Answers

It's bad in both cases, but ESLint isn't picking up the first case.

You can see this by changing the labels array at run-time. If you increase or decrease the length it blows up (because an unexpected number of hooks was called, as per the exception), if you don't it doesn't re-render as expected. See here for an updated version of your demo.

(The reason that the rule exists is because under the hood, hooks need to be called in the same order every time. Obviously, if the number of hooks varies between renders then the order changes.)

In your specific case, because your array is fixed length and not changing then your code will work fine (no matter what the linter says). However, it's still a dangerous pattern and best avoided.

In terms of how to handle it better, if you need more than basic state management something like useReducer would work. You can have as may labels as you like and define setter/getter actions that take the label as an argument.

like image 97
Will Jenkins Avatar answered Jan 26 '26 23:01

Will Jenkins


Let analyze the two scenarios:

const labels = ['one', 'two'].map(useGetSet);

In this case the useGetSet is called inside the hook is called "inside custom React Hook function".

const labels = ['one', 'two'].map((l) => useGetSet(l))

In this case the useGetSet is called inside an anonymous function, so the "rule of hooks" is broken.

So basically:

In the first scenario: Hook > call of the hook

In the second scenario: Hook > anonymous function > call of the hook

Related to the last question:

Why this difference, shouldn't they be equivalent?

No, they are not equivalent.

In the first case the callback function reference is the function named, by you, useGetSet.

In the second case the callback function reference is a new function, defined anonymously.

An interesting explanation about why this rule is so important can be found in the documentation, in particular in this section:

https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

But also consider that there are cases when it is safe to disable the linter rule, as explained in depth here:

Why can't React Hooks be called inside loops or nested function?

like image 26
Emanuele Scarabattoli Avatar answered Jan 26 '26 22:01

Emanuele Scarabattoli



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!