Creating custom hook for fetching data in react
Tags: react, reactnative, javascript, typescript
Published: 2021-06-07 03:24:42

## Prerequisite to implement the customHooks
* Knowlegde of functional components, and react hooks.
* Libraries/Packages required: Redux, Axios.
* Placeholder API such as: [jsonplaceholder](https://jsonplaceholder.typicode.com/)

## Topics covered in this blogpost:
1. Architectural pattern that is used for creating custom hook with axios.get().
2. Implementation of custom hook.
3. Usage in the actual component.
4. More usecases for the custom hook like addition of adhoc redux actions, and multiple REST method implementation.


## Architecture of our custom hook.
* Every custom hook generally returns a state, it may or may not have a useEffect inside it. Let us name our custom hook as `useFetch`. According to the [Rules of Hook](https://reactjs.org/docs/hooks-rules.html) every custom hook should have `use` as a keyword in front of it to recognize it as a hook.
* `useFetch` is going to consist of the following blocks:
  * **Parameters**:
    * List of parameters such as URL, the method type, bodyand, headers.
  * **State Block**:
    * This will consists of all the local states i.e. `useState`
  * **useEffect Block**:
    * This will consists of the logic for `axios` call that we are going to make to the server.

Below diagram will provide a clearer view on how `useFetch` is designed:

![useFetch UML](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z66p2egz5n1a04k4hy3f.png)

## Implementing our custom hook: `useFetch`
1. Let us first create a function which accepts a url as a parameter. We will also include the local state variables to this function.

```JS
const useFetchData = (url) => {
  const [isLoading, setIsLoading] = useState(false);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);
};
```

the function described above will consists of `useStates` as 
 * `isLoading` for checking if the API has fetched the data or is still fetching the data, 
 * `apiData`: If data is fetched successfully then the data is stored in `apiData` variable, 
 * `serverError`: If there is any error in fetching the data from the API endpoint then we will store that error in `serverError` variable.

2. We are going to add an `useEffect` react hook. This hook will consists of an `axios.get(URL)` call on the requested URL.

```JS
const useFetch = (url) => {
  const [isLoading, setIsLoading] = useState(false);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      try {
        const resp = await axios.get(url);
        const data = await resp?.data;

        setApiData(data);
        setIsLoading(false);
      } catch (error) {
        setServerError(error);
        setIsLoading(false);
      }
    };

    fetchData();
  }, [url]);
};
```

The `axios` call is enclosed in a async function named `fetchedData`. It consists of `try...catch` block. Once the data is awaited we store it in `apiData` using `setApiData`. If you have observed I have also set `isLoading` to `true` at the start of the useEffect. This is done intentionally because we want to show the loader when the API has initiated a call to the server. Once we get the response with 200 status we set the `isLoading` to `false` using `setIsLoading`.

If by some chance there is any error, we set the `serverError` state to `error` along with `isLoading` state to `false`.

3. Finally we are going to return all the local state variables as an object.

```JS
const useFetch = (url) => {
  const [isLoading, setIsLoading] = useState(false);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      try {
        const resp = await axios.get(url);
        const data = await resp?.data;

        setApiData(data);
        setIsLoading(false);
      } catch (error) {
        setServerError(error);
        setIsLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { isLoading, apiData, serverError };
};
```

## Usage in the actual component
Let us look at an example were we can use our custom hook `useFetch`. Below is the code of the `index.js` file.

```JS
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import useFetch from "./useFetch";

const App = () => {
  const { isLoading, serverError, apiData } = useFetch(
    "https://jsonplaceholder.typicode.com/posts/1"
  );
  return (
    <div>
      <h2>API data</h2>
      {isLoading && <span>Loading.....</span>}
      {!isLoading && serverError ? (
        <span>Error in fetching data ...</span>
      ) : (
        <span>{JSON.stringify(apiData)}</span>
      )}
    </div>
  );
};

const rootElement = document.getElementById("root");

ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);
```

Now just like any React hook we can directly use our custom hook to fetch the data. As you can see, `isLoading` and `serverError` can be used for conditional rendering of the component for displaying nice error messages.

## Additional use cases
We can have additional use cases such as Adding adhoc redux actions, Generalizing the API methods etc. Below is the brief overview of the two use cases.

1. Adding Adhoc redux actions:
You can also integrate redux to your application and add an API response to your global state. Modification to the useFetch would look like this:

```JS
const useFetchData = (url) => {
  const dispatch = useDispatch();
  const [isLoading, setIsLoading] = useState(false);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      try {
        dispatch(fetchApiData());
        const resp = await axios.get(url);
        const data = await resp?.data;

        dispatch(fetchApiSuccess(data));
        setApiData(data);
        setIsLoading(false);
      } catch (error) {
        setServerError(error);
        dispatch(fetchApiFailure());
        setIsLoading(false);
      }
    };

    fetchData();
  }, [dispatch, url]);

  return { isLoading, apiData, serverError };
};
```
`fetchApiData`, `fetchApiSuccess`, and `fetchApiFailure` are thunks which call the specific redux actions along with storing the data onto the redux global state.

2. Generalizing API methods
Our `useFetch` is currently performing only `GET` request. Ideal scenario is to have our `useFetch` to perform all types of request such as `POST`, `PUT` etc. Following code will make a generalized axios call.

```JS
const useFetch = (method, url, body) => {
  const [isLoading, setIsLoading] = useState(false);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      try {
        const resp = await axios({
          method: method,
          url: url,
          data: body
        });
        const data = await resp?.data;

        setApiData(data);
        setIsLoading(false);
      } catch (error) {
        setServerError(error);
        setIsLoading(false);
      }
    };

    fetchData();
  }, [url, method, body]);

  return { isLoading, apiData, serverError };
};
```
Usage will be the same as that of index.js. Only thing that is changing is the function definition:

```JS
const { isLoading, serverError, apiData } = useFetch(
    "GET",
    "https://jsonplaceholder.typicode.com/posts/1",
    {}
  );
```

These are some of the use cases which I think can most commonly used. You can you the above custom hook to fetch the data and enhance it as per our needs. 

You can find the code used in this blogpost at the following sandbox URL: 
https://codesandbox.io/s/react-custom-hook-sample-dcuf4

Feel free to reach out to me @
<div style="display:flex;flex-direction:row;justify-content:center">

[![Linkedin Badge](https://img.shields.io/badge/linkedin-%230077B5.svg?&style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/keyur-paralkar-494415107/)

[![Mail Badge](https://img.shields.io/badge/email-c14438?style=for-the-badge&logo=Gmail&logoColor=white&link=mailto:furkanozbek1995@gmail.com)](mailto:keur.plkar@gmail.com)

[![Twitter Badge](https://img.shields.io/badge/twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/keurplkar)

[![Github Badge](https://img.shields.io/badge/github-333?style=for-the-badge&logo=github&logoColor=white)](https://github.com/keyurparalkar/)

</div>