All Articles

React, Redux, and API's Part Three: Redux

In the last post we looked a bit deeper in to using React to talk to APIs in a DRY manner. In this post, we will look to introduce Redux to manage our application state and talk to our API.

We won’t look at why you might want to redux, but instead we will look at how you can use thunks as interfaces to talk to APIs and move all your logic out of your components that we had in part one and part two.

There’s a bit of assumed knowledge of redux in this post. At the minimum you should understand:

  1. Why you would want to use redux in your application
  2. What an “action” is and does
  3. What a “reducer” is and does
  4. What a “middleware” is and does

It would also help if you have an understanding of thunks.

The Redux docs are fantastic and you should definitely read through those if you are unsure on the above.

Fair warning: this post is a bit long!

Thunks

Thunks are very useful in redux applications as they give you access to state, via a function called getState, and dispatch. It means you can change your action creators from being simple functions that return an object, to a function which returns an inner function that will allow you to check your state or dispatch multiple actions.

Redux-thunk summarises the above as:

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

Cool, so that’s a lot of information so let’s look at what that translates to in terms of code.

Example of traditional Redux action vs. thunk

Let’s take the example of an action to update a customer ID.

// customerActions.js

const updateCustomerID = customerID => {
	type: "Customer.UPDATE_CUSTOMER_ID",
	payload: {
		customerID
	}
}

In this action creator it receives a customerID and then will return an action to update it. But what if the magic business rules said that we only wanted to only update the customerID if there wasn’t one already set in the store?

One way would be to connect your component that was updating the customerID and check there before firing the action off. But what if you had another component that needed to the same thing? Or two? Or three? It would be heavy duplication everywhere.

Thunks allow us to avoid that:

// customerActions.js

const updateCustomerID = customerID => (dispatch, getState) => {
	const state = getState();
	const { customerID } = state.customer;

	if (customerID === null) {
		dispatch({
			type: "Customer.UPDATE_CUSTOMER_ID",
			payload: {
				customerID
			}
		});
	}
}

Here we can use the thunk to check that the customerID is not already set and if it’s not we can update it. This way, we can avoid having to duplicate tonnes of code all over our application.

Talking to APIs with thunks

OK, so that was a lot of background on using thunks, but it was kind of necessary before we talk about how you can use this to talk to APIs.

One particular pattern I like when dealing with APIs in redux is to fire of actions when the request starts, the request successfully completes, and the request fails. This allows you to set up your reducers to handle various different outcomes and in turn you can update your UI. For example, you could show a loading spinner when posts are being fetched from an API.

Here’s how we might talk to the posts API using a thunk:

// postsActions.js

const loadPosts = () => async (dispatch, getState) => {
	dispatch({
		type: "Posts.LOAD_POSTS_REQUEST"
	});

	const fetchConfig = {
		method,
		headers: new Headers({ "Content-Type": "application/json" }),
		mode: "cors"
	}

	const response = await fetch(`https://jsonplaceholder.typicode.com/${endpoint}/`, fetchConfig);

	if (response.ok) {
		try { 
			const data = await response.json();
			dispatch({
				type: "Posts.LOAD_POSTS_SUCCESS",
				payload: {
					data
				}
			});
			return;
		} catch (error) {
			dispatch({
				type: "Posts.LOAD_POSTS_FAILURE"
			});
		}
	}

	dispatch({
		type: "Posts.LOAD_POSTS_FAILURE"
	});
}

In this sample we fire an action to note that we’re fetching posts, then we fire an action when it successfully completes, and then we also can fire an action if it fails.

Here’s how this translates to our component:

// Posts.js
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";

import PostList from "./PostList";
import { loadPosts as loadPostsAction } from "./postsActions";

class Posts extends Component {
	componentDidMount() {
		const { actions: { loadPosts } } = this.props;
		loadPosts();
	}

	render() {
		const { posts } = this.props;

		return (
			<PostList posts={posts} />
		)
	}
}

const mapStateToProps = state => ({
	posts: state.posts
});

const mapDispatchToProps = dispatch => ({
	actions: bindActionCreators({
		loadPosts: loadPostsActions
	})
});

export default connect(mapStateToProps, mapDispatchToProps)(Posts);

You can see we’re no longer coupling our container component to fetching the data, or having to have internal state for the component. This will give us greater flexibility as our application grows in size and complexity.

I’ve deliberately left out the reducers, but essentially it would just need to handle those three actions and update the store accordingly.

Up Next:

In the next post we will look at how we can make this Redux code more DRY as we expand our application to also include actions for comments.

Published 3 Nov 2018

Front-end software developer; husband; bicycle rider.
Patrick Gordon on Twitter