React custom hooks : A simple explanation🐱‍👤

_ali_

ali

Posted on June 15, 2021

React custom hooks : A simple explanation🐱‍👤

Hi all 👋

React 16.8 V comes with several useful features and one being hooks. Hooks are super useful. Some of the predefined hooks are useState, useEffect. Today we will see how to write a custom hook 🎣.

But before we start, why do we need to write a custom hook ?
A common scenario where we might need a custom hook is to extract the duplicate code/logic and separate it into a single function, thus re using the same code whenever needed.
Traditionally we had two popular ways to share stateful logic in react.

  • Render props or
  • Higher order components (HOC).

But with hooks we have more flexibility and ease. A custom hook is defined by react as :

A JavaScript function whose name starts with ”use” and that may call other Hooks.

Before we write a custom hook, lets see the rules of hooks.

Rules of hooks

Basically we have two rules when using hooks and they are :

  • Only Call Hooks at the Top Level 🔝
  • Only Call Hooks from React Functions

The first rule says, not to use hooks in conditions as React relies on the order in which Hooks are called.

The second rule says to only use hooks from react functions or use a hook in a custom hook.

We'll cover in-depth about rules of hooks in a separate post but do remember these points while writing your custom hook. Also do remember to name your hook with "use".

Write a simple custom hook ✍

Let's say we have the following functional component which displays a username and it is fetched from the back-end using a axios get call. Axios is just a library which enables us to make api calls. The data fetching is achieved using the useEffect hook which executes a axios request on component mounting. Do note that I haven't used the cleanup function for ease of understanding, but in an ideal scenario, we must use the cleanup function. I have explained the importance of cleanup function in a separate post on useEffect. The below code fires an axios get request to fetch the username. It also renders loading message or an error message during/after the execution.

export default function DisplayUserName() {

  const [userName, setUserName] = useState(null);
  const [loading,setLoading] = useState(false);
  const [error,setError] = useState(null);
  //Runs on mounting of the DisplayUserName component
  useEffect(() => {
    setLoading(true);
    axios
      .get('http:localhost:5000/getusername')
      .then((res) => {
          setUserName(res.data);
          setLoading(false);
      })
      .catch((err) => {
            setLoading(false);
            setError(err);
        });
  }, []);

  return (
    <div className="App">
      {loading ? "Loading ..." : <h1> Username : {userName} </h1>}
      {error && <h2> {error} </h2>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The above code works fine. But a proper application would have many number of api calls from different components and is obvious that we may have to repeat the same logic in every component. So to avoid all these boilerplate, we could separate this common logic into our own custom hook which returns the object containing response, error and loading as shown below.

export const useGetQuery = (url) => {
  const [response, setResponse] = useState(null);
  const [loading,setLoading] = useState(false);
  const [error,setError] = useState(null);
  // Runs whenever the dependency url changes
  useEffect(() => {
    setLoading(true);
    axios
      .get(url)
      .then((res) => {
        setResponse(res.data);
        setLoading(false);
      })
      .catch((err) => {
        setLoading(false);
        setError(err);
      })
  }, [url]);

  return { response,loading,error };
};
Enter fullscreen mode Exit fullscreen mode

Here we extract the code logic and keep it in a separate function. Do note that our name of the custom hook starts with use. This is done so that react understands this is a hook and shows appropriate warnings or errors for our code and react highly recommends that we follow the same convention. Also note that the returning object contains the response, loading and error values. These values can be used in any component which uses our new custom hook. The below code uses our custom hook to display the username, error, loading message whenever applicable.

export const DisplayUserName = () => {
     const url = 'http:localhost:5000/getusername';
     const {response,loading,error} = useGetQuery(url);
    return (
    <div className="App">
      {loading ? "Loading ..." : <h1> Username : {response} </h1>}
      {error && <h2> {error} </h2>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Common errors while using custom hooks 🐞

Seen this error ?

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem

or this ?

React Hook "useGetQuery" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.

or this ?

React Hook "useGetQuery" is called in function "handleClick" that is neither a React function component nor a custom React Hook function.

This issue may have occurred when a developer tries to call a custom hook in a callback. Remember the rules that I told you about in the beginning? Well this error says just that, that you have violated the rule which says to "use your hook only in a React functional component" and not to use them anywhere else.

Custom hook in a callback

As per the rules of hooks, we can't use them in a condition or in a callback. But what if we have to use the logic in a callback ?.
A quick way around is as given below.

export const useFetchQuery = (time) => {
  const [response, setResponse] = useState(null);
  const [loading, setLoading] = useState(false);
  const fruits = [🍎, 🍌, 🥭, 🍇, 🍉];
  const fetchDetails = (time) => {
    setLoading(true);
    setResponse(null);
    //Logic to update the response to a random fruit
    setTimeout(() => {
      setResponse(fruits[Math.floor(Math.random() * 10) % 4]);
      setLoading(false);
    }, time);
  };
  //The fetchDetails method is returned from our custom hook
  return { fetchDetails, response, loading };
};

Enter fullscreen mode Exit fullscreen mode

A above code is pretty self explanatory. I have used setTimeout function to emulate a api call. The function fetchDetails updates the response as a random fruit from the fruit array. It also updates the loading state.

Notice how we have returned the fetchDetails function from our custom hook useFetchQuery. This function can be used in our callback as shown below.

  const { fetchDetails, response, loading } = useFetchQuery(2000);

  const handleClick = () => {
    //Notice the fetchDetails method which is used below
    fetchDetails(2000);
  };
  return (
    <div className="App">
      <button onClick={handleClick}> Click Here </button>
      {loading && <h1>Loading ...</h1>}
      {response && <h1>Random Fruit : {response}</h1>}
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

On clicking the button, handleClick callback is executed which in turn calls the fetchDetails function from our custom hook.

Here is the codesandbox :

Conclusion

React provides us with several hooks. The developer has more flexibility as he/she can write a custom hook whenever needed. Do remember the rules of the hooks while writing your custom hook. Hope you understood the basics of creating your custom hook. Do follow for more posts similar to this. Until next time 🤟

💖 💪 🙅 🚩
_ali_
ali

Posted on June 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related