Custom Hooks
React supports custom hooks. This means that in addition to the hooks that come with React, like useState
and useEffect
, we can create our own hooks, things like useInterval
and useOnScroll
.
Custom hooks are the best thing about React hooks. They make the entire API shine, and honestly, I've been anxiously waiting for us to reach this point of the course so that I could share them with you!
If you've been struggling to understand why people like hooks, hopefully this lesson will make it clear! 😄
Not as scary as it sounds
The first thing to understand is that we aren't really inventing our own hooks.
When I first heard the term "custom hook", it sounded intimidating, like something only the most advanced power user would use. Hooks are hard enough when we use the default ones!
The lightbulb moment for me came when I started thinking of them as custom hook combos.
Let's look at an example.
Video Summary
- So over the past few lessons, we've been facing some of the gnarliest parts of modern React. The
useEffect
hook is notoriously tricky! - While hooks definitely have their downsides, they have one incredible upside: the ability to create custom hooks.
- In this video, we revisit an exercise we saw earlier, our digital clock:
import React from 'react';import format from 'date-fns/format';
function Clock() { const [time, setTime] = React.useState(new Date());
React.useEffect(() => { const intervalId = window.setInterval(() => { setTime(new Date()); }, 1000);
return () => { window.clearInterval(intervalId); }; }, []);
return ( <p className="clock"> {format(time, 'hh:mm:ss a')} </p> );}
Custom hooks allow us to bundle multiple React hooks into one. Here's what this looks like:
function Clock() { const time = useTime();
return ( <p className="clock"> {format(time, 'hh:mm:ss a')} </p> );}
function useTime() { const [time, setTime] = React.useState( new Date() );
React.useEffect(() => { // Effect logic const intervalId = window.setInterval(() => { setTime(new Date()); }, 1000);
return () => { // Cleanup logic window.clearInterval(intervalId); }; }, []);
return time;}
I've created a new function called useTime
, and this function manages everything related to the time
state, including the effect that keeps it up-to-date!
The function returns the time
state variable, because that's what is necessary inside the Clock
component:
const time = useTime();
There are two advantages to custom hooks:
- Code organization. By moving the state/effect out of the component, it makes it easier to understand what the
Clock
component does. By giving the hook a name likeuseTime
, we can make clear what it does. - Code reuse. Because I've moved this logic into its own function, I can share that function with other components. If any component needs to know what time it is, in a way which is automatically integrated with React state, all I have to do is import this function.
At first glance, this might not seem that impressive. After all, we can already share logic between components by using plain ol’ JavaScript functions. What makes "custom hooks" special?
It's true that regular functions allow us to reuse logic, but let's look again at this custom hook.
I've highlighted the lines that are "pure JS" logic:
function useTime() { const [time, setTime] = React.useState( new Date() );
React.useEffect(() => { // Effect logic const intervalId = window.setInterval(() => { setTime(new Date()); }, 1000);
return () => { // Cleanup logic window.clearInterval(intervalId); }; }, []);
return time;}
We do have some "pure" JS stuff here — creating Date()
objects, starting intervals — but most of this stuff is “React logic”.
The thing that makes custom hooks special is that they integrate with the React lifecycle.
Our useTime
hook isn't designed to calculate an output given some inputs, like a typical pure JS function. Instead, it creates a React state variable, and manages a long-running interval, including cleaning it up when the parent component unmounts. This isn't something a typical JS function can do.
One final gotcha: your custom hooks need to start with the word “use”. If we rename our custom hook to getTime
, for example, we'll get lint warnings that look like this:
React Hook "React.useState" is called in function "getTime" that is neither a React function component nor a custom React Hook function.
(This lint warning is not shown in the video because at the time this video was filmed, I hadn't yet integrated a linter into the playground.)
Remember when we were talking about the Rules of Hooks? The first rule is:
- Hooks have to be called within the scope of a React application. We can't call them outside of our React components.
The one exception to this rule is custom hooks. We're allowed to use React hooks like useState
and useEffect
in a function if that function is a custom hook. We declare that a function is a custom hook by starting it with the word “use”, and making sure it follows the Rules of Hooks (eg. we can't call useTime
conditionally, within our component).
Here's the code from the video above:
Code Playground