Using React hooks to asynchronously make API requests

Hooks are officially out 🎊! I just recently started working with them, but I can already say that they definitely feel like a game changer. Not only do they enable the creation of stateful functional components, but the ability to create and reuse custom hooks across components is great for keeping your components DRY!

After learning and experimenting with the basics of the useState hook, I wanted to see how hooks could be used to perform asynchronous actions such as making requests to an external API. I decided to try the useReducer hook for this because I am already familiar with using a middleware in Redux for doing the same.


♻️ Reduce ♻️ Reduce ♻️ Reduce ♻️

In order to implement useReducer, first we need to create… a reducer of course! If you’re familiar with Redux, or the Flux architecture that it implements, this will be pretty straight forward. If you’re not, don’t worry, because this is still pretty straight forward and I’m going to walk through and explain everything as best I can. I won’t be diving into the pattern itself, but if you want to learn more, there’s already a ton of resources available. To get started, there are a couple of things to consider:

  1. What we need to keep in our state
  2. How we want to structure our state

I chose the following, but feel free to modify this to conform with your needs.

// reducer.js
export const initialState = {
status: null,
response: null,
exception: null,
};

This structure allows us to easily check the status of the current request, and separates the response and exception (if any) data.

Now that we have our state data structure, we need to implement 3 actions.

  1. Fetching — the action to dispatch when an api request is in progress
  2. Success — the action to dispatch when an api request completes successfully
  3. Error — the action to dispatch then an api requests result in error

I’ve separated my action types into a separate file, but you can include them in the reducer file if you like. Just make sure to export them so that you can use them whenever you need to reference an action type, such as in your action creators.

Action Types — prefixed with the name of the hook

Now we can use these types to create our reducer actions.

Reducer

The second argument of our reducer is just the action itself. We can de-structure the action here because this is a pretty basic reducer. Take note that the FETCHING action spreads and returns the initialState instead of the current state. This is done to cleanup the state from any previous requests.


Take Action! ⚡️

Now we need to write some action creators in order to dispatch these actions. These are simple functions that take any data that needs to be added to the state, such as the response data, and return an object containing the action type and this data. The reducer above handles updating the state with this data.

Action Creators — used to dispatch our actions

The Captain

Finally, we need to tie all this together with our custom hook. Let’s start by implementing the useReducer hook; a hook within a hook 😅.

// index.js
import { useReducer } from 'react';
import reducer, { initialState } from './reducer';
// inside useApiRequest function
const [state, dispatch] = useReducer(reducer, initialState);

We pass in our reducer and initialState from earlier and the method returns an array that we de-structure into the state object and dispatch function. We’ll return the state object for use in the implementing component (more on that later) and use the dispatch function for updating our state within our custom hook.

Next, we need to wrap our code above inside a function like below.

// index.js
import { useReducer } from 'react';
import reducer, { initialState } from './reducer';
const useApiRequest = (endpoint, { verb = 'get', params = {} } = {}) => {
const [state, dispatch] = useReducer(reducer, initialState);
}
export default useApiRequest;

This is our hook. This function accepts an endpoint string (required) and an options object (optional) that describes the request.

Now we need to write one final function, our makeRequest function. This is our callback that we return to the component so that it can invoke the request. It will also dispatch our actions from earlier in order to update the state object so that the component knows the status of the request. We end up with the following.

The Hook — not complicated at all

I used axios to make the request, but you can replace this with fetch, superagent or whatever library you’d like.

While the function is asynchronous in order to wait for the request to complete before updating the state with the success or error data, we’ll want to take action based on the state change and not the makeRequest promise being resolved/rejected. For this reason, it doesn’t return any data, or reject the promise for any reason.


Putting It To Work 👷

Now that our hook is complete, lets take a look at how we can implement it into a component. All you really need to do is invoke the hook function from inside of a functional component like so.

const [state, makeRequest] = useApiRequest('https://www.foo.com/bar', { verb = 'post', params: { baz: '1' });

Once completed it looks something like this.

API Request Component — really simple example implementation

This is a simple functional component that implements the hook to make a request to a sample API. The API returns information about a user based on a userId. The block that configures the hook is

const [{ status, response, exception }, makeRequest] = useApiRequest(
`https://jsonplaceholder.typicode.com/users/${userId}`,
{
verb: 'get',
}
);

The makeRequest callback we receive is simply added to a button below as the onClick prop. The rest of the code here allows changing the userId and uses the state object for displaying the results. Not bad at all, right?


So this is actually the first custom hook that I wrote. I’m actually amazed at how straight forward writing hooks is, and how little friction there was for me to get this working. Big props to the React team on this one 👊.

I wasn’t able to find any existing write-ups for this that didn’t use a context, so if you’re reading this, I hope it was helpful and that you learned something new. Please let me know what you think in the comments below. 🙏

Cheers. 🍻

A live demo and full source can be found below on CodeSandbox.