React/ReactJS: Custom Hooks
Before Hooks, Render Props and Higher-Order Components are the two ways state is shared between components in React.
However, logic between two functions is better shared by extracting it to a third function. It works both for Function Components and Hooks as they are functions.
A Custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
Below we construct a simple Custom Hook called usePi.js
which uses the two built-in Hooks useState
and useEffect
and returns the value of π. It is, of course, practically a useless example.
Following the Rules of Hook, we call the existing Hooks at the top level even inside a Custom Hook. They are not to be called inside loops, conditions, or nested functions. They are also not to be called from regular JavaScript functions.
We pass an empty array []
as the second argument to useEffect
, which serves the purpose of componentDidMount()
, signalling that it is called only once after the component is mounted.
// usePi.js
import { useState, useEffect } from "react";
const usePi = () => {
const [pi, setPi] = useState(0);
useEffect(() => {
setPi(Math.PI);
}, []);
return pi;
}
export default usePi;
We import and use the above Custom Hook usePi.js
inside a function component:
import React from "react";
import usePi from "./usePi";
const valueOfPi = (props) => {
const pi = usePi();
return (
<div>
The value of pi is {pi}
</div>
);
}
export default valueOfPi;
Practically, we need not go about in such roundabout manner (to the extent of creating Custom Hooks) just to get the value of π.
We pick a much useful example in the section below.
The useFetch()
Custom Hook
Now we will take up a different example, something useful practically compared to the first above. We build a Custom Hook to fetch some data on load of a component using the Fetch API
. We pass an empty array []
to useEffect()
indicating that it is a one-time load on initiation of the component.
// useFetch.js
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => setData(data));
}, []);
return data;
}
export default useFetch;
Now we will use the above Custom Hook we just created in another component. We fetch a random Chuck Norris joke by passing the free URL https://api.chucknorris.io/jokes/random
into our usefetch()
custom hook, which retrieves the following JSON:
{
"categories": [
"dev"
],
"created_at": "2020-01-05 13:42:19.324003",
"icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
"id": "fivi0z5lt8gp_6vt3cc8pw",
"updated_at": "2020-01-05 13:42:19.324003",
"url": "https://api.chucknorris.io/jokes/fivi0z5lt8gp_6vt3cc8pw",
"value": "If Chuck Norris writes code with bugs, the bugs fix themselves."
}
And inside another component, we just render the icon_url
and value
fields.
import React from "react";
import useFetch from "./useFetch";
const ChuckNorrisJoke = () => {
const data = useFetch("https://api.chucknorris.io/jokes/random");
return (
<div>
<img src={data.icon_url}/>
<p>{data.value}</p>
</div>
);
}
export default ChuckNorrisJoke;
The output is as follows.
NOTE. If you refresh the page, the joke will change.