Updated: Apr 28
Custom React Hooks is a very convenient way to encapsulate logic and pass the data down the rendering tree.
The rules for custom React Hooks are quite simple:
Consider this very naive implementation of a custom hook that encapsulates permissions mapping for some shared document service:
As you can see this hook is pretty simple - it makes use of another custom hook that returns an array of permissions and maps it into a simple permitted/non-permitted dictionary. This will allow for better code reuse across the application and help us avoid code duplication in every place where we need this check:
The only problem is that this logic will run on every re-render. In case of a small array it's negligible but if it's a large array then we're in trouble. A simple addition to the custom hook can solve this issue:
This way the permissions will be rematched only when the permissions array changes.
Case solved! Or is it?
Official React documentation can give us a hint what might be wrong with this approach:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
How does a custom Hook get isolated state? Each call to a Hook gets isolated state. Because we call useFriendStatus directly, from React’s point of view our component just calls useState and useEffect. And as we learned earlier, we can call useState and useEffect many times in one component, and they will be completely independent.
Effectively this means that if we call useIsPermitted from two different components (or even twice from the same component), the logic will be executed for every instance of the useIsPermitted invocation, even thought we use useMemo inside.
Custom Hooks with Context
A solution for this would be combining a custom hook with a context.
Let's revise our useIsPermitted hook implementation:
Now the logic is scoped to a specific context provider. That means that if we use this provider only once at the root of the application, the logic will be executed only once:
Of course we can decide to put the provider lower in the rendering tree so that the logic will be executed only when a relevant part is rendered, but bottom line is - we have more control over the granularity now.
When to Use What
While a hook with context seems to be a more robust solution in terms of performance and memory consumption, it doesn't mean that you should always go with this approach.
There is place for both approaches, but it's important to understand the implications of each one of them.
Here is a short checklist that will help you decide on the right approach:
Use Pure Hook when:
Custom hook state must be isolated (different per instance) OR
No heavy calculations are performed in the custom hook OR
You only use the hook once in the application (as a way to pass data down the rendering tree)
Use Hook with Context when:
The whole subtree must share the hook's state OR
There is a heavy calculation performed inside the hook and you want it to run as seldom as possible
Follow me if you liked the article, comment/send a message here or DM on Twitter if you have any questions.