Async API Fetching with Redux Toolkit 2020

avatar
Marshal Murphy
June 10, 2020 - 8 MIN READ

I remember the old days of Redux fondly.

Long ago, when hooks were not yet on anyones’ mind.

When Redux required us to wrap every React component in higher-order connect() components, with functions mapping dispatch and state to props.

A barbaric time where our actions lived one place, and our reducers another.

Where both thunks and Redux devtools required additional dependencies, and we had to write a whole bunch of code to do what boils down to very little: updating application state.

But then, whispers began of a creature rising in the distance: React Hooks.

And with it followed a tidal wave of change…

Feature

…class components, banished!

Lifecycle methods, deprecated, littering the ground like skeletons.

But from this change, life bloomed…


Alright, enough of this ridiculous metaphor. 😅

We both know you are not here for cheesy writing.

Redux Toolkit!

aka: RTK.

With Redux Toolkit we can approach Redux in a new way, with far less code, while leverage hooks.

In this tutorial I will show you how.

Together we are going to begin with create-react-app, set up Redux using RTK, and write a thunk to fire off an API fetch with async/await to retrieve some data from a recipes database as our main component mounts.

We will store this data in Redux using slices, write logic for error handling, and retrieve and display the data in the browser.

All of the above will be accomplished using both React and Redux Hooks, and less code than we would using the older Redux methods.

Here’s the final repo if you’d like to skip ahead.

Ready?


Setup

Let’s begin by leveraging create-react-app and the adding the few dependencies we'll need.

In your console, navigate to where you keep your projects (ie. Documents, Desktop, Tax-Receipt-2015) to set up the React boilerplate.

I’ll be calling this project redux-toolkit-2020.

> npx create-react-app redux-toolkit-2020

image

With the install completed, let’s change into our project directory and add the dependencies we’ll need for this project, then boot up the app.

> cd redux-toolkit-2020
> yarn add redux react-redux @reduxjs/toolkit
> yarn start

image

Alright, works as it’s supposed to. Shut down the app with control + c and let's get to work.

We’re going to start by removing all the boilerplate files in /src.

That’s right, delete it all.

cd into the now empty /src folder and create the files index.js, index.css, and App.js:

> cd src
> touch index.js index.css App.js

Your project tree should now look like so:

image

Now let’s set up a simple React component for us to start with.

In index.js, add the following block of code:

Then in App.js create a simple React component.

Lastly, let’s just establish some basic styles so our UI looks passably okay.

Add the following to index.css:

Now start the app again to ensure all is working as expected…

image

… and we are looking good.

The last thing we need to do is to install the Redux Devtools extension for Chrome.

This browser extension will allow us to examine the contents of our Redux store from the developer panel, and is a must-have for anyone working with Redux.

Head over to the Chrome store and install that now if you haven’t already.

image

With this super-basic React app set up for us to build upon, let’s get started with Redux Toolkit!


Redux Store Setup with RTK Slices

Redux Toolkit abandons the use of higher-order components with the connect() method and embraces the use of Hooks and Slices.

React Hooks allow us to handle updating local state and using lifecycle methods in functional components with useState() and useEffect().

Redux Hooks allow us to retrieve data from the store and dispatch actions to update the store with useDispatch() and useSelector().

A slice, created with createSlice() from Redux Toolkit, is a method that allows us to create initial state, actions, reducers, and selectors for our Redux store, all in one file.

Let’s begin by creating a new directory in /src called /slices.

> mkdir src/slices
> touch src/slices/index.js src/slices/recipes.js

Our index.js file in /slices will be our rootReducer.

Here is were you will import all of your various reducers for the Redux state and combine them with combineReducers().

In slices/index.js, create our rootReducer.

Our rootReducer is only importing a single reducer, recipesReducer, called so because we will be querying a database of recipes and storing that data in our store as an array of recipe objects.

Add the following code to slices/recipes.js and we will go over what we are doing here together.

This may look a little overwhelming at first, so let’s go through what is happening, starting at the top.

At the top of the file we import createSlice from Redux Toolkit, and we follow this by creating the initial state.

The initial state is just that: it is the base object of our recipes state that we create to store our recipes data. In this initial state object we have 3 values: loading, hasErrors, and recipes.

Since we will be fetching data from an external database, we need a way to indicate the success or failure of that request to the UI so that we may update it accordingly, and we accomplish this by firing actions to update the states of loading, hasErrors, and recipes.

Beneath the initial state is our slice and where the magic of Redux Toolkit happens.

We create the slice with the method createSlice() that we imported, and we store this in a variable to be exported as our reducer.

Within the slice object we establish the name (recipes), pass in the initial state object that we created above, and define the reducers.

The reducers take in state and optionally a payload and update the state accordingly.

For example, in the block for the getRecipesSuccess() function, when called it will receive a payload object (an array of recipes objects) and will add the payload data to our recipes array in the state, while also signalling that the query has been successful by setting loading and hasErrors to false.

With our recipes slice established and our root reducer created, all we need to do is to create the redux store and wrap our app in it in index.js at the folder root (where we first import App.js):

Great!

Now just open the devtools in Chrome and you will see our store nicely set up and awaiting some data 😄.

image


Redux Toolkit: Access the Store with useSelector()

In order to pull data from our Redux store into a functional component, we need to write a selector function alongside our recipes slice.

In /slices/recipes.js, add the following selector. You can add it directly after the const recipesSlice() block.

The selector merely exports the current state of the recipes array.

Then, back in App.js, we will use this selector by importing the useSelector() hook from react-redux, as well as the recipesSelector we just created in our recipes slices.

In your Chrome developer console you will see the result of our selector: an empty recipes array.

image

Now let’s get that data, yes?


Redux Toolkit: Query API & Update Store with useDispatch()

Let’s go back to our slices/recipes.js file and write a thunk.

A thunk is a middleware that lets us call a function to do something, and which results in dispatching Redux actions to update the store.

We are going to write an asynchronous thunk function that will be dispatched when our app loads for the first time.

This thunk function will begin by updating our Redux loading state to true.

It will then await a fetch call to TheMealDB API within a try/catch block.

On success, we will dispatch an action to update the store with our recipes.

On failure, we will update the store with the error returned.

In /slices/recipes.js, export our 3 slice reducers as actions, then create a separate function called fetchRecipes().

recipes.js will now look something like this:

Take a little time to follow the information flow starting from fetchRecipes().

  1. When our thunk its triggered, it first updates loading to true by dispatching the getRecipes() function.
  2. We then try and await a response from the api using fetch.
  3. On success from the fetch, we store the data by passing it as the payload to getRecipesSuccess(data). It is now available to us in our Redux store.
  4. Otherwise, on failure, we update the states accordingly and surface the error.

Now, let’s use the React hook useEffect() and the Redux hook useDispatch() to fire off this thunk on initial mount of App.js.

Update your App.js with the following:

Now if we take a look at our Redux devtools, we should see that our fetch was successful, and that our recipes array now has a whole bunch of data in it! 😁

image


UI: Map and Display Data

The last thing to do is to display this data for the user in our UI.

Since we already implemented our selector to grab the states for recipes, loading, and hasErrors, we can do this fairly effortlessly.

First though, let’s update one little thing in our thunk.

You may have noticed that the API returned a meals array containing the recipe objects. We don’t really need the meals key, so update the line in slices/recipes.js in the thunk function fetchRecipes() to dispatch getRecipesSuccess and pass data.meals, like so:

Now in App.js, update it one last time to return hasErrors and loading if those are true, and to map over the recipes array if the fetch is successful.

Behold!

Our final product!

image


Conclusion

Redux has grown considerably since I began using it about 3 years ago, and personally, I love these improvements.

Being able to easily update the store and handle app state logic with hooks is a godsend.

Be sure to read the Redux Toolkit API to learn more about what is possible.

Here’s the final repo for the finished app.

Marshal out.

Marshal Murphy 2020