React Custom Hook example
Tags: react, javascript, webdev, todayilearned
Published: 2022-04-22 04:01:00

In addition to the familiar Hooks like <code>useState</code>, <code>useEffect</code>, <code>useRef</code>..., React also allows us to create custom Hooks with unique features that extracts component logic into reusable functions. Let's learn what, why, when and how to write a Custom Hook in React through a simple <code>useAxiosFetch</code> example.

Tutorial from Bezkoder:
[React Custom Hook example](https://www.bezkoder.com/react-custom-hook/)

<h2>What are React Custom Hooks?</h2>
From version 16.8, React Hooks are officially added to React.js. Besides built-in Hooks such as: <code>useState</code>, <code>useEffect</code>, <code>useCallback</code>..., we can define our own hooks to use state and other React features without writing a class.

A Custom Hook has following features:
<ul>
	<li>As a function, it takes input and returns output.</li>
	<li>Its name starts with <code>use</code> like <code>useQuery</code>, <code>useMedia</code>...</li>
	<li>Unlike functional components, custom hooks return a normal, non-jsx data.</li>
	<li>Unlike normal functions, custom hooks can use other hooks such as <code>useState</code>, <code>useRef</code>... and other custom hooks.</li>
</ul>

You can see that some libraries also provide hooks such as <code>useForm</code> (React Hook Form), <code>useMediaQuery</code> (MUI).

<h2>Why and When to use React Custom Hooks</h2>
Custom hooks give us following benefits:
<ul>
	<li>Completely separate logic from user interface.</li>
	<li>Reusable in many different components with the same processing logic. Therefore, the logic only needs to be fixed in one place if it changes.</li>
	<li>Share logic between components.</li>
	<li>Hide code with complex logic in a component, make the component easier to read.</li>
</ul>

So, when to use React custom hook?
<ul>
	<li>When a piece of code (logic) is reused in many places (it's easy to see when you copy a whole piece of code without editing anything, except for the parameter passed. Split like how you separate a function).</li>
	<li>When the logic is too long and complicated, you want to write it in another file, so that your component is shorter and easier to read because you don't need to care about the logic of that hook anymore.</li>
</ul>

<h2>React Custom Hook example</h2>
Let's say that we build a React application with the following 2 components:
- <code>TutorialsList</code>: get a list of Tutorials from an API call (GET /tutorials) and display the list.
- <code>Tutorial</code>: get a Tutorial's details from an API call (GET /tutorials/:id) and display it, but the interface will be different.

```jsx
import React from "react";
import { Routes, Route } from "react-router-dom";

import Tutorial from "./components/Tutorial";
import TutorialsList from "./components/TutorialsList";

function App() {
  return (
    <div>
      ...

      <div>
        <Routes>
          <Route path="/tutorials" element={<TutorialsList />} />
          <Route path="/tutorials/:id" element={<Tutorial />} />
        </Routes>
      </div>
    </div>
  );
}

export default App;
```

You can find the complete tutorial and source code for the React App at:
<a href="https://bezkoder.com/react-hooks-crud-axios-api/">React Hooks CRUD example with Axios and Web API</a>

![react custom hook example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwd5tn92eox2843g6zxg.png)

<h3>When not using React Custom Hooks</h3>
Let's see how we've made for simple API call from the components <code>TutorialsList</code> and <code>Tutorial</code> without using React Custom Hooks.

We set up axios base URL and headers first .

<em>http-common.js</em>
```js
import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json"
  }
});
```

Then we use <code>axios.get()</code> to fetch data from API with <code>response</code> result or <code>error</code>.

<em>components/TutorialsList.js</em>
```jsx
import axios from "../http-common.js";

const TutorialsList = () => {
  const [tutorials, setTutorials] = useState([]);
  const [currentTutorial, setCurrentTutorial] = useState(null);
  const [searchTitle, setSearchTitle] = useState("");

  useEffect(() => {
    retrieveTutorials();
  }, []);

  const retrieveTutorials = () => {
    axios.get("/tutorials")
      .then(response => {
        setTutorials(response.data);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  const findByTitle = () => {
    axios.get(`/tutorials?title=${searchTitle}`)
      .then(response => {
        setTutorials(response.data);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  return (...);
}
```

<em>components/Tutorial.js</em>
```jsx
import { useParams} from 'react-router-dom';

const Tutorial = props => {
  const { id }= useParams();

  const initialTutorialState = ...;
  const [currentTutorial, setCurrentTutorial] = useState(initialTutorialState);

  const getTutorial = id => {
    axios.get(`/tutorials/${id}`)
      .then(response => {
        setCurrentTutorial(response.data);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  useEffect(() => {
    if (id)
      getTutorial(id);
  }, [id]);

  return (...);
}
```

<h3>Using React Custom Hook</h3>
Look at the code above, you can see that both components above have a very similar logic. They all call API to get data, save the response data into the state to update again when the data is successfully retrieved. The only difference is that they render different UI and different URL when calling API.

```js
axios.get(...)
  .then(response => {
    ...
  })
  .catch(e => {
    ...
  });
```

We can reduce the repetition by creating a custom hook <code>useAxiosFetch()</code> for reuse as follows:

<em>customer-hooks/useAxiosFetch.js</em>
```js
import { useState, useEffect } from "react";
import axios from "axios";

axios.defaults.baseURL = "http://localhost:8080/api";

export const useAxiosFetch = (url) => {
  const [data, setData] = useState(undefined);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(true);

  const fetchData = async () => {
    try {
      const response = await axios.get(url);
      setData(response.data);
    } catch (error) {
      setError(error);
      setLoading(false);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return { data, error, loading };
};
```

From now, in 2 components <code>TutorialsList</code> and <code>Tutorial</code>, we just need to use custom hook <code>useAxiosFetch</code> without worrying too much about the logic inside it. Just know it receives <code>url</code> and returns 3 values: <code>data</code>, <code>loading</code> and <code>error</code>.

We can make the custom hook more dynamic. For example, we want to pass more details of the request (<code>method</code>, <code>url</code>, <code>params</code>, <code>body</code>...) instead of only <code>url</code>. Furthermore, we may need to call <code>fetchData()</code> method outside the hook.

Let's modify a few code like this.

<em>useAxiosFetch.js</em>
```js
import { useState, useEffect } from "react";
import axios from "axios";

axios.defaults.baseURL = "http://localhost:8080/api";

export const useAxiosFetch = (axiosParams) => {
  const [data, setData] = useState(undefined);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(true);

  const fetchData = async () => {
    try {
      const response = await axios.request(axiosParams);
      setData(response.data);
    } catch (error) {
      setError(error);
      setLoading(false);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return { data, error, loading, fetchData };
};
```

Let's use this React custom Hook in our components:

<em>components/TutorialsList.js</em>
```jsx
import React, { useState, useEffect } from "react";
import { useAxiosFetch } from "../custom-hooks/useAxiosFetch";

const TutorialsList = () => {
  const [tutorials, setTutorials] = useState([]);
  const [searchTitle, setSearchTitle] = useState("");

  const { fetchData, data, loading, error } = useAxiosFetch({
    method: "GET",
    url: "/tutorials",
    params: {
      title: searchTitle,
    },
  });

  useEffect(() => {
    if (data) {
      setTutorials(data);
      console.log(data);
    } else {
      setTutorials([]);
    }
  }, [data]);

  useEffect(() => {
    if (error) {
      console.log(error);
    }
  }, [error]);

  useEffect(() => {
    if (loading) {
      console.log("retrieving tutorials...");
    }
  }, [loading]);

  const onChangeSearchTitle = (e) => {
    const searchTitle = e.target.value;
    setSearchTitle(searchTitle);
  };

  const findByTitle = () => {
    fetchData();
  };

  // ...

  return (
    <div>
      <div>
        <input
          type="text"
          placeholder="Search by title"
          value={searchTitle}
          onChange={onChangeSearchTitle}
        />

        <button type="button" onClick={findByTitle} >
          Search
        </button>
      </div>

      <div>
        <h4>Tutorials List</h4>

        {loading && <p>loading...</p>}

        <ul className="list-group">
          {tutorials &&
            tutorials.map((tutorial, index) => (
              <li key={index} >
                {tutorial.title}
              </li>
            ))}
        </ul>
      </div>
    </div>
  );
};

export default TutorialsList;
```

<em>components/Tutorial.js</em>
```jsx
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useAxiosFetch } from "../custom-hooks/useAxiosFetch";

const Tutorial = () => {
  const { id } = useParams();

  const initialTutorialState = ...;
  const [currentTutorial, setCurrentTutorial] = useState(initialTutorialState);

  const { data, loading, error } = useAxiosFetch({
    method: "GET",
    url: "/tutorials/" + id,
  });

  useEffect(() => {
    if (data) {
      setCurrentTutorial(data);
      console.log(data);
    }
  }, [data]);

  useEffect(() => {
    if (error) {
      console.log(error);
    }
  }, [error]);

  useEffect(() => {
    if (loading) {
      console.log("getting tutorial...");
    }
  }, [loading]);

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    setCurrentTutorial({ ...currentTutorial, [name]: value });
  };

  // ...

  return (
    <div>
      {currentTutorial ? (
        <div>
          <h4>Tutorial</h4>

          { loading && <p>loading...</p>}

          <form>
            <div>
              <label htmlFor="title">Title</label>
              <input
                type="text"
                id="title"
                name="title"
                value={currentTutorial.title}
                onChange={handleInputChange}
              />
            </div>
            <div>
              <label htmlFor="description">Description</label>
              <input
                type="text"
                id="description"
                name="description"
                value={currentTutorial.description}
                onChange={handleInputChange}
              />
            </div>

            <div>
              <label>
                <strong>Status:</strong>
              </label>
              {currentTutorial.published ? "Published" : "Pending"}
            </div>
          </form>

          ...

        </div>
      ) : (
        <div>
          <br />
          <p>Please click on a Tutorial...</p>
        </div>
      )}
    </div>
  );
};

export default Tutorial;
```

<h2>Conclusion</h2>
In this tutorial, you've known what, why and when to use a React Custom Hook. You also implement the Custom Hook for API call using Axios with an example.

<h2>Further Reading</h2>
More Practice:
- <a href="https://bezkoder.com/react-hooks-crud-axios-api/">React Hooks CRUD example with Axios and Web API</a>
- <a href="https://bezkoder.com/react-hooks-file-upload/">React Hooks File Upload example with Axios</a>
- <a href="https://bezkoder.com/react-form-validation-hooks/">React Form Validation with Hooks example</a>
- <a href="https://bezkoder.com/react-hooks-jwt-auth/">React Hooks: JWT Authentication (without Redux) example</a>
- <a href="https://bezkoder.com/react-hooks-redux-login-registration-example/">React + Redux: JWT Authentication example</a>

Serverless:
- <a href="https://bezkoder.com/react-firebase-hooks-crud/">React Hooks + Firebase Realtime Database: CRUD App</a>
- <a href="https://bezkoder.com/react-hooks-firestore/">React Hooks + Firestore example: CRUD app</a>