When you start using React, you will quickly be introduced to the idea of hooks. Perhaps, if you do not read the docs, you will not even know that you are using hooks. But chances are, in writing a react component, you will find a use case for useState or useEffect at the very least.
This name, hooks, brings to mind the idea of lifecycle hooks that users of earlier versions of React will be familiar with, like willComponentUpdate. But other than that, these mysterious functions seem to share only the prefix use, their actual functionality varying widely.
Some are responsible for storing local state, some for updating it, some for accomplishing side effects, some for asynchronous activities like data fetching. If you are familiar with ember.js, you may notice that collectively, the builtin hooks offered by react essentially accomplish the functionality of ember’s many computed properties (among other things), with a much different interface.
Background: Motivating the question
As you dig around the docs, you will notice it is possible to create your own custom hooks, and indeed, that this ability seems to be heavily touted as one of the main advantages of hooks. If you are like me, you may find yourself asking: why would I want to do that? After all, at first glance, a hook seems to be no more than a function that starts with this mysterious prefix, use.
Of course, there will be times when you want to encapsulate some reusable logic (the idea of helpers or utils may come to mind if you are familiar with ember), but the question remains: why encapsulate it in a hook? What benefits are conferred by using use?
When to use custom hooks
The answer, it turns out, becomes clearer when you dig a bit deeper into the hooks docs, and in particular, when you read the rules of hooks section. There are, in fact, only two rules of hooks:
- Only call hooks at the top level (not in nested functions, loops, conditionals, etc.)
- Only call hooks from a react function component or a custom hook
The second rule, then, gives us an idea of why we might want to write our own hooks, and in fact, gives the motivation for them: custom hooks are used to extend other hooks. In particular, they are used when the functionality that you need to reuse depends on another hook call. Since builtin hooks are used so extensively throughout a React application, this situation comes up quite often.
Take, as the simplest example, the useState hook. Suppose that you have several components that all need to display a particular state property. For example, a user’s name. You might use the useState hook to store this value:
const [userName, setUserName] = useState(user.name)
where user is an application-level state object.
This seems simple enough, and indeed, implementing this across multiple components would be as easy as writing the above line of code in each component. However, now suppose that the user object depends on data that is fetched asynchronously from the backend, and is not guaranteed to be populated by the time the page loads. Now, you may find yourself having to refactor your implementation to something like:
const [userName, setUserName] = useState(‘’)
useEffect(() => {
setUserName(user.userName)
}, [user])
Notice above, you now have two separate tasks that accomplish a common goal, and you will need to reuse them across multiple components. So, encapsulation becomes attractive. And, because the logic that you wish to encapsulate involves react hooks, you will, by necessity, encapsulate it in a react hook, and not a plain javascript function, as a result of rule 2 of hooks*.
*Point of fact: |
React will actually allow you to break this rule without complaining, and in this simple example, it would work fine. If you use eslint, and install eslint-plugin-react-hooks, you will get a compiler warning or error for breaking this and the other rule, but otherwise, you would actually be able to compile and render this example just fine. An in-depth discussion of why the rules of hooks are what they are, and why you should avoid breaking them, is beyond the scope of this post, but for further reading see this clarifying note from Dan Abramov. But, if you accept the rules of hooks proposed by the React team as law, then you will need to encapsulate this logic in a custom hook. |
Extracting a custom hook
Here is an implementation of our minimal custom hook that combines the state and effect from above:
function useName(){
const [userName, setUserName] = useState(‘’)
useEffect(() => {
setUserName(user.userName)
}, [user])
Return userName;
}
Then, in a component:
function SomeReactComponent(){
…
Const userName = useName()
…
}
Recap/TLDR
So, there you have it, a simple heuristic and motivating example of how and when to use custom React hooks. To reiterate, use custom hooks when you want to encapsulate some reusable logic across different components, and the reusable logic itself involves hooks. Because hooks are so heavily ingrained in the implementation of modern React components, expect to reach for custom hooks much of the time when you need to encapsulate reusable logic.
Further reading
At a deeper level, you could read the above note (see point of fact) and linked post, and understand that custom hooks are not magic, and are, in fact, plain javascript functions that start with the prefix use (which allows React to identify them) and contain other hooks.
Furthermore, you could understand that there is technically no penalty for breaking the rules of hooks, and that they exist as helpful guidelines imposed by the React team. This knowledge will now help you understand how or when to use custom hooks, so if that is all you are after, stick to the above advice. Thanks for reading!