When to Create Custom React Hooks
If you're familiar with React, you probably have heard about React hooks, which are nothing more than functions used to supply a value, side effect, or both, within a React component.
Here's a silly example showing a counter, which alerts the user after they've reached a certain threshold.
function Counter() {
// `useState` provides a value
const [count, setCount] = React.useState(0);
// `useEffect` provides a side effect
React.useEffect(() => {
if (count === 10) {
window.alert('you have counted to 10! great job!');
} else if (count >= 100) {
window.alert('ok maybe you should get on with your day now');
}
}, [count]);
return <button onClick={() => setCount(() => count + 1)}>{count}</button>;
}
Here's how you could refactor this to use a custom hook useCounter
.
function useCounter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
if (count === 10) {
window.alert('you have counted to 10! great job!');
} else if (count >= 100) {
window.alert('ok maybe you should get on with your day now');
}
}, [count]);
return {
count,
increment: () => setCount(() => count + 1),
};
}
function Counter() {
const {count, increment} = useCounter();
return <button onClick={increment}>{count}</button>;
}
You can take it a step further and move the logic in useEffect
to yet
another hook.
function useAlertOnCountThreshold(count) {
React.useEffect(() => {
if (count === 10) {
window.alert('you have counted to 10! great job!');
} else if (count >= 100) {
window.alert('ok maybe you should get on with your day now');
}
}, [count]);
}
function useCounter() {
const [count, setCount] = React.useState(0);
useAlertOnCountThreshold(count);
return {
count,
increment: () => setCount(() => count + 1),
};
}
function Counter() {
const {count, increment} = useCounter();
return <button onClick={increment}>{count}</button>;
}
Hopefully you're starting to see how custom hooks can be made using
other React hooks (e.g. useState
, useEffect
, etc) as building blocks.
Some people think all hooks should be custom hooks. I personally disagree with this approach. I find too many levels of indirection can often be cumbersome and confusing.
In my own project, I probably wouldn't have created the useAlertOnCountThreshold
hook in the previous example. That was just to demonstrate how it could be done.
The decision to create a custom hook is
- sometimes a style preference, like what we saw in the previous example,
- and sometimes a necessity in order to reuse functionality. We saw this building the notification state management library.
When you're faced with a choice of optionally creating a custom hook (i.e. to make a code style decision), go with the approach that's consistent with your team's style guide. If you're working alone, go with the approach you find makes the most sense.
A common mistake when beginning to write custom hooks is to make things hooks that should really be regular JavaScript functions.
If you're function is dealing with React state using useState
, useEffect
, etc,
then use a custom hook. Name the function with the use
prefix e.g. useSession
, useModal
, useThing
,
etc.
Otherwise, just make a normal JavaScript function.
Summary
- Create a custom hooks to wrap logic that
utilizes the standard hooks (e.g.
useState
,useEffect
, etc). If your function is not working with React state, you want a normal function, not a React hook. - Create a custom hook if it makes the code easier to maintain.
For example,
useEffect
hooks with many lines of business logic can quickly make a component hard to work with. Often, extracting out logic into a custom hook can help. - Create a custom hook if it will be reused in many places. This case often comes up when using the Context API. We saw this when we built a notification state management library.
- Avoid creating a custom hook if the business logic is simple enough.
In the earlier example, I wouldn't
make the custom hook
useAlertOnCountThreshold
since it's so simple, but that's personal preference. I made the custom hook to show how you could make a custom hook there.