React Custom Hooks

What are React hooks?

When we use React class components, we used to manage state and other React lifecycle features inside those class components using React lifecycle methods like ”componentDidMount”.

Since React brought us function component features, it needed to have those lifecycle methods same as the React class components. Therefore, Hooks are functions that let you “hook into” React state and lifecycle features from function components same as the class components.

What is a custom hook and why we need it?

Custom hooks allow us to build our own hooks for function components. Basically, this lets you to extract component logic into reusable functions. Let’s assume that we have a feature of fetching data from some kind an API and this feature uses in few other function components.

In this case we need to re-write the same code to fetch data in all the components which needed this feature. So, we can create a custom hook for data fetching and use it everywhere we want.

Now we have a reusable feature of data fetching and let’s create a custom hook for this.

How to make a custom hook?

Normally, I used to keep these custom hooks inside a “hooks” folder under “src” folder. Also, these hooks need to start with “use” word and that may call other hooks. Also, we can’t use hooks inside a class component since it’s introduced for function components.

First we need to make a function with the “use” word and export it as below. For data fetching purpose I’m going to use axios and also, we need to use “useState” & “useEffect” hooks which are already provide by React.

import { useState, useEffect } from "react";
import axios from "axios";
function useFetchData(url, timeout = 0) {
console.log(url, timeout);
const [data, setData] = useState(null);
const [fetchState, setFetchState] = useState({
loading: false,
error: false,
});
return [fetchState, data];
}
export default useFetchData;

This “useFetchData” function take two parameters. The “url” is the one we're going to fetch data from. This url for an API call. Also the “timeout” is set to 0 by default in case we don’t need to set a timeout for data fetching.

This code has used two states to store our fetched data in the “data” variable and also to keep the fetching state in the “fetchState” variable. This “fetchState” is an object which contains “loading” state and “error” state. At the end of this function we need to return those two states.

Let’s create a function for data fetching.

const getData = async (url, timeout) => {
setFetchState((prev) => {
return { ...prev, loading: true };
});
await axios({ url, timeout })
.then(({ data }) => {
// console.log("data -->", data);
setData(data);
})
.catch((error) => {
// console.log("error -->", error);
setFetchState((prev) => {
return { ...prev, error: true };
});
});
setFetchState((prev) => {
return { ...prev, loading: false };
});
};

We take the same two parameters “url” & “timeout” which are coming from the “useFetchData” function. Then we pass them to the “axios” as it’s arguments. Before start the data fetching, we need to set the state of “loading” to “true”. Because we are starting to fetch data.

Let’s move to “axios”, I’m using JavaScript promises. It’s very easy to use rather than adding try catch block. In the “. then” we need to set our “data” state and also, in case we got an error we can catch it inside the “.catch” and set the “error” state to “true”.

At the end of the function we can set our “loading” to false since we have already done our fetching part.

Alright! Now we have finished the fetching part. Let’s connect this to our “useFetchData” and see how it looks.

import { useState, useEffect } from "react";
import axios from "axios";
function useFetchData(url, timeout = 0) {
console.log(url, timeout);
const [data, setData] = useState(null);
const [fetchState, setFetchState] = useState({
loading: false,
error: false,
});
const getData = async (url, timeout) => {
console.log(url);
setFetchState((prev) => {
return { ...prev, loading: true };
});
// we can replace axios with JavaScript fetch
await axios({ url, timeout })
.then(({ data }) => {
// console.log("data -->", data);
setData(data);
})
.catch((error) => {
// console.log("error -->", error);
setFetchState((prev) => {
return { ...prev, error: true };
});
});
setFetchState((prev) => {
return { ...prev, loading: false };
});
};
useEffect(() => getData(url, timeout), [url, timeout]);
return [fetchState, data];
}
export default useFetchData;

As you can the above code. I’ve placed the getData function inside our “useFetchData” hook and the It’s calling insde the “useEffect” hook. Now this should work as we want. Let’s see how we can import it to another folder.

import useFetchData from "../hooks/useFetchData";
const rootURL = "https://api.github.com";
function Dashboard() {
const [fetchState, data] = useFetchData(`${rootURL}/users/mcperera`, 4000); //timeout: 4000, 4 seconds timeout
console.log("Dashboard --> ", fetchState, data);
return <h1>data.login</h1>;
}
export default Dashboard;

Now we have a custom hook to fetch data using any url. I’m using github API to fetch given user details. So, As seen in the code. I pass the “url” and “time out” to our “useFetchData” hook. Then it returns what we want as “fetchState” & “data”.

This is a very useful hook when come to DRY-ing our code. Happy Coding 🎉