React.js is currently the most popular JavaScript library for front end developers. Invented by Facebook, but available as an open-source project, it’s used by developers and corporations all over the world.
React really changed the way we build single-page applications — one of its greatest features is hooks. Hooks were introduced not long ago and enabled us to use functional components instead of class-based components while dealing with the state. Besides built-in hooks, React offers us ways to implement our own custom hooks.
Here are some of my favorite examples for custom hooks and their implementation that you can also use in your applications and projects.
useTimeout - React Hook
With this hook, we can implement setTimeout using a declarative approach. First, we create a custom hook with a callback and a delay. Then we use the useRef hook to create a ref for the callback function. Finally, we make use of useEffect twice. One time for remembering the last callback and one time for setting up the timeout and cleaning up.
The example shows an implementation of a timer:
usePrevious - React Hook
This is another great custom hook that we can use in our applications. With it, we can store props or the previous state. First, we create a custom hook that takes in a value. Then we use the useRef hook to create a ref for the value. Finally, we use useEffect to remember the latest value.
The example shows an implementation of a counter.
useClickInside - React Hook
If you deal with event handling for clicking inside of wrapped components then the useClickInside hook is the right choice for you. First, we create a custom hook that takes in a ref and a callback to handle the click event. Then we make use of useEffect to append and clean up the click event. Finally, we use useRef to create a ref for the component to be clicked and pass it to the useClickInside hook.
useClickOutside - React Hook
The useClickOutside hook is quite similar to the useClickInside hook but it takes care of clicking outside a wrapped component and not inside. So again, we create a custom hook that takes in a ref and a callback to handle the click event. Then we make use of useEffect to append and clean up the click event. Finally, we use useRef to create a ref for the component and pass it to the useClickOutside hook.
useFetch - React Hook
The useFetch hook can be used to implement fetch in a declarative way. First, we use useState to initialize the response and error state variables. Then we use useEffect to asynchronously call fetch and update the state. Finally, we return an object that contains the response/error variables.
The example shows a way to fetch a character from the Star Wars API and render its name:
useInterval - React Hook
If you want to implement setInterval in a declarative manner you can use this hook called useInterval.
First, we have to create a custom hook taking in a callback and a delay. Then we use useRef to create a ref for the callback. Finally, we use useEffect to remember the latest callback and to set up the interval and clean up.
The example shows an implementation for a custom ResourceCounter that can be used in a browser game, for example.
useComponentDidMount - React Hook
This hook is a small example of how to execute a callback right after a component is mounted. For the second argument, we simply use useEffect with an empty array, to execute the provided callback once as soon as the component is mounted.
useComponentWillUnmount - React Hook
useComponentWillUnmount is similar to the example above but will execute a callback as soon as the component is unmounted. So we use useEffect again with an empty array as the second argument to execute the provided callback right before the cleanup
These are some of my favorite examples of custom React hooks that you can use in your applications.
If you are looking for inspiration and front end projects check out my site!
If you like what I write and want to support me and my work, please follow me on Twitter to learn more about programming, making, writing & careers🥰
Top comments (19)
Nice examples! I'm sure that you are aware of it, and know how to fix it. So for anyone else reading the article and my comment here: there's a small problem with the
useFetch
hook implementation.It currently would not react to URL or options argument changes, since the internally used
useEffect
doesn't specify them as dependencies, so the response result wouldn't change should the request change (e.g. different URL or different query parameters). In fact fetch only runs once on component mount, because the dependencies array is empty.In order to fix that the url and the options need to be added to the dependencies array as follows:
Referential Equality Problem
The problem with React dependencies is that changes are detected through referential equality tests. So a hook with dependencies only gets re-run when the following is true for one or more of the dependency values:
That works fine for primitive values such as numbers, strings and booleans.
In case of functions or objects such as the options parameter this is a bit of a problem. Even when the data in an object literal does not change, two instances of
{}
are not referentially equal. JavaScript would create new instances for those object literals on each render run.Three workarounds for that:
Let the user of
useFetch
take care of memoizing the options viauseMemo
, so it's "referentially stable" and does not change from render run to render run. But that would be sorting out the problem from the wrong end in my opinionFrom within
useFetch
, serialize the options argument as string, and add that string to the dependencies array. The serialization must be stable though, so we don't get different results between render runs (i.e. different order of properties)Use an use effect hook that is able to do deep dependency comparisons. Let's call it
useEffectDeep
. That could be either an exercise for writing another custom hook, or can be downloaded from here (or other places): github.com/kentcdodds/use-deep-com...Awesome, thank you very much for the detailed analysis!
🔥🔥
Great to see you are embracing the hook feature! And awesome you share your findings.
There are however a few small problems with your hooks.
useTimeout
anduseInterval
You are storing the callback into a ref which is great because you make sure you will allways call the latest version. You however set it using
useEffect
. This means if the callback changes, the change will be picked up in the next frame. Theoretically there is a chance that the callback has changed when the timeout (or interval) fires. You can fix it like this:usePrevious
This might actually be expected behavior, but this does not actually show the previous 'different' value. If you component rendered again with no value change
usePrevious
would return the same value. So the actual name should beuseValueFromPreviousRender
.You could see this behavior when you add another state to the
MoneyCount
component that toggles a boolean when you click another button. The hook would be a bit more involved if you wanted to show the previous value after a change.useClickInside
anduseClickOutside
This hook adds and removes the event listener on each render. It would be smart to use a similar strategy to
useTimeout
with a ref for the callback. On top of that, its a good idea to set the dependency array.As a side note: in theory the ref you are given could be a function ref, if that is the case your code would fail. The easiest way to guard for this is by creating and returning the
ref
from the hook itself.useFetch
I think this is already mentioned but it only executes once during the lifetime of the component.
In real world applications you often want to know the response status code. I think it would be a great addition to add that.
useComponentDidMount
anduseComponentDidUnmount
These hooks capture the callback that is passed in. This means that if the callback changes, those changes will not be picked up. For
useComponentDidMount
this will most likely never cause a problem, foruseComponentDidUnmount
I can see this causing problems more often.If someone in my company would create these hooks I would say that these hooks do not have the correct name. Their names refer to the component lifecycle and that is something hooks helps us to move away from. We want to focus on the actual effect and its lifecycle. On top of that, for new developers (that have only been exposed to React with hooks) the concepts of mount and unmount are unknown.
All in all great examples. I hope my comments are of value to you. I strongly suggest you install the eslint plugin as this would warn you for most problems.
Nice comment! But I have a puzzle: why borther to use
I think we can eliminate the use of
useRef
, just use thecallback
argument in the placecallbackRef.current
.@wangyouhua Great question: What is the difference between these versions?
You can see the difference in the usage, in this example I want to log the value of
someState
after 5 seconds:As you can see version 2 and 3 have a bit different behavior. Version 2 is broken (and caught by the linting rules). Version 3 has unexpected behavior, it resets the timeout when
someState
is changed.This has to with the 'capturing' of the callback. Once you pass a function to
setTimeout
that function can not be changed. Primitive values it references are captured. In order to get the expected result and don't put the burden of that correct handling on the user of the hook, we keep a reference to the 'current' callback.So if you are using callbacks in hooks this is a great pattern:
Thank you very much for this detailed comment, this helps a lot!
blog.post.replace(/pictures/gi, 'code'));
I think I would break the
loading
out ofres.response
inuseFetch()
.Why? Because if you load something else (a different URL), then
res.response
already exists and won't cause the "loading..." to be shown... so the loading flag really needs to be before the start offetch()
and aftersetData()
useComponentWillUnmount
this way (in the image), then won't it be true if the component is rendered 20 times, then useEffect() sets it up 20 times to run it 20 times? That's because you are callinguseEffect()
20 times in totalHi, I enjoyed reading this. I didn't find use cases for all of them in a recent project. I'm using a Mediator pattern to handle fetch requests. I did repeat my self !DRY. But the abstraction is a refactor for another iteration.
I do agree with the comments which point out the errors. I would suggest making the fixes and updating this article. There's an option which allows you to do that.
And if you haven't already done so. Check out React Server Components. I'm sure you love them.
Hi Simon,
Thanks a lot for the examples ill definitely use them!
Do you have a repo where you store the code? If you only share images I cannot use them unless I type them again.
Hey, glad you like them. I wanted to try something different this time and animate others to actually do type it on their own. But I will prepare a short repo with the code!
Here's the one for useTimeout: codesandbox.io/s/youthful-spence-n...
You threw me off with the variable named seconds since it only increments every 5 seconds and then executes only once so I wanted to check it out.
Working code examples with posts like this would be helpful
Will make it that way next time 😊
Hi. Nice article, thx a lot! Very useful for newer like me😊
ComponentDidMount and ComponentWillMount are now marked as unsafe to use on the latest version of React.JS
The useTranslation hooks is awesome too 😉 dev.to/adrai/how-to-properly-inter...