The Hooks API in React provides a few built-in hooks—which we can either use in components or in our own hooks with self-contained reusable logic.
React is a JavaScript library used for creating interactive web frontend applications. It is one of the most popular libraries because of its easy-to-use API.
Most recent versions of React provide the Hooks API, which lets us create frontend JavaScript apps with function components and the logic behind them. React provides a few built-in hooks which we can use in components or use them to create our own hooks with self-contained reusable logic.
In this article, we will look at how to create our own custom React hooks.
Hooks are the basic building blocks of the Hooks API. They are functions that we call in components or custom hooks that let us add dynamic logic to components or custom hooks.
React provides a few basic hooks that let us add logic to our React apps. By convention, hooks all start with use
in their names.
They can only be called at the top level of components and custom hooks so they can all be called in the order they are listed.
Some basic hooks include the useState
, useEffect
, useRef
and useMemo
hooks.
The useState
hook lets us store states in our React components or custom hooks.
It returns an array with the state and state setter function.
For instance, we can use it by writing:
import { useState } from "react";
export default function App() {
const [number, setNumber] = useState(Math.random());
return (
<div className="App">
<button onClick={() => setNumber(Math.random())}>
pick random number
</button>
<div>{number}</div>
</div>
);
}
to call the useState
hook and set the initial value of the number
state to a random number returned by Math.random
.
We call the setNumber
function in the click event handler to set the number
state to a new random number value.
Also, we can update the state value based on the previous value of the state. To do this, we call the state setter function with a callback that takes the previous state value.
For example, we write:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<button onClick={() => setCount((oldCount) => oldCount + 1)}>
increment count
</button>
<div>{count}</div>
</div>
);
}
to call useState
to return the count
state and the setCount
function.
We add a button that calls setCount
with a callback that takes the old value of count
and returns the latest count
value, which is the old count
value plus 1.
Then when we click the button, we see the incremented count
value displayed.
The useEffect
hook lets us commit side effects in our React components or custom hooks.
It takes a callback that’s called whenever the states or props it is watching have changed if an array of states and props it is watching is set as the second argument. Otherwise, the callback is called on every render if no second argument is set. If we pass in an empty array as its second argument, then the callback is called only when the component mounts.
For instance, we can use it to watch for clicks on the whole screen.
To do this, we write:
import { useEffect, useState } from "react";
export default function App() {
const [mouseLoc, setMouseLoc] = useState({});
const { pageX, pageY } = mouseLoc;
useEffect(() => {
const onMouseMove = (e) => {
const { pageX, pageY } = e;
setMouseLoc({ pageX, pageY });
};
window.addEventListener("mousemove", onMouseMove);
return () => window.removeEventListener("mousemove", onMouseMove);
}, []);
return (
<div className="App">
{pageX}, {pageY}
</div>
);
}
to call the useState
hook to define the mouseLoc
state.
Then we get the pageX
and pageY
values, which are the x and y coordinates of the mouse cursor location on the screen.
Next, we call the useEffect
hook with a callback that calls window.addEventListener
with 'mousemove'
and the onMouseMove
function to add the onMouseMove
function as the mousemove event listener for the window
, which is the current browser tab.
We return a function in the useEffect
hook callback that’s called whenever a state or prop being watched is changed or when the component is unmounted when an empty array is passed in as the second argument into
useEffect
.
We call window.removeEventListener
to remove the onMouseMove
as the mousemove
event listener. We then display the pageX
and pageY
values on the screen. As a result, when we move the mouse around, we see the mouse cursor’s coordinates displayed on the screen.
The useRef
hook lets us store values that we want to keep when the component is re-rendered.
For instance, we write:
import { useEffect, useRef } from "react";
export default function App() {
const ref = useRef();
useEffect(() => {
ref.current.focus();
}, []);
return (
<div className="App">
<input ref={ref} />
</div>
);
}
to call the useRef
hook to create a ref.
Then we can assign it a value by passing the ref
to the ref
prop of an HTML element or component.
We can also assign a value we want to keep when the component re-renders as the value of the ref.current
property.
In the useEffect
hook, we call ref.current.focus
to set focus on the input since ref.current
has the input element as its value.
This is because we set the ref
prop of the input to the ref
returned by the useRef
hook.
The useMemo
hook lets us cache values computed from states.
import { useMemo, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const totalCount = useMemo(() => {
return count + count2;
}, [count, count2]);
return (
<div className="App">
<button onClick={() => setCount((oldCount) => oldCount + 1)}>
increment count
</button>
<button onClick={() => setCount2((oldCount2) => oldCount2 + 1)}>
increment count 2
</button>
<div>{totalCount}</div>
</div>
);
}
to define the count
and count2
states with the useState
hook.
Then we create the totalCount
derived state with the useMemo
hook.
We call it with a callback that returns the sum of count
and count2
to set that as the value of totalCount
.
And we call useMemo
with an array with count
and count2
in it to make the useMemo
recalculate the sum when either of the
values change.
Therefore, when we click either button, we see the totalCount
update.
There are a few other hooks that React comes with. We can use any hooks to create our own custom hooks. Let’s see how.
We can create hooks and use them easily since they are all pure functions. This means that they take inputs and return outputs derived from the inputs. Therefore, it is very easy for us to create and use hooks in our components.
To start, we create the useScreenSize.js
file. Then we write:
import { useEffect, useState } from "react";
const { innerWidth, innerHeight } = window;
export default () => {
const [screenSize, setScreenSize] = useState({
innerWidth,
innerHeight,
});
useEffect(() => {
const onResize = (e) => {
const { innerWidth, innerHeight } = e.target;
setScreenSize({ innerWidth, innerHeight });
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return screenSize;
};
to create the useScreenSize
hook.
We call useState
to create the screenSize
state.
Then we call the useEffect
hook with a callback that listens for the resize
event with window.addEventListener
.
We use the onResize
function as the resize event listener. In it, we get the window width and height from the e.target.innerWidth
and e.target.innerHeight
properties respectively.
We call setScreenSize
to set the screenSize
state to an object with the window
innerWidth
and innerHeight
values in pixels.
And we return a callback that calls removeEventListener
to remove the resize
event listener when we unmount the component.
Finally, we return the screenSize
state so we can access it in our components.
Next in App.js
, we write:
import useScreenSize from "./useScreenSize";
export default function App() {
const { innerWidth, innerHeight } = useScreenSize();
return (
<div className="App">
<div>
{innerWidth}x{innerHeight}
</div>
</div>
);
}
to import and call the useScreenSize
hook created earlier.
We get the innerWidth
and innerHeight
properties from the object returned by the hook with the destructuring syntax. Then we display them on the screen.
As a result, when we resize the browser tab or window, we see the latest width and height of the browser tab or window.
As we can see, we just return the values we want in our custom hook, and we can access them easily from a component or another hook. This is the power of the React Hooks API.
We can easily compose logic since they are all self-contained and we just use their return value without needing to worry about what they do.
Also, this means we can mock them easily in our tests since we just need to mock the return value of the hook in our component tests.
The React Hooks API lets us create frontend JavaScript apps with function components and logic.
React provides a few built-in hooks—including the useState
, useEffect
, useRef
and useMemo
hooks—which we can
use in components or use them to create our own hooks with self-contained reusable logic.
John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.