All Articles

React, Redux, and API's Part Four: Redux (DRY)

In the last post we looked at how we could use redux to interface with our API. In this post we’re going to take a look at how we can make that code more re-usable so we don’t have to repeat ourselves.

Here’s the example from the previous post. It uses thunks to allow us to dispatch three distinct actions and it calls the API using fetch.

// postsActions.js

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

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

	const response = await fetch(`https://jsonplaceholder.typicode.com/posts/`, 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"
	});
}

Let’s instead abstract away the calling of the API to a new file called apiHelpers.js. This function will take an object as its only argument which contains the following args:

  1. config - the overrides to the fetchConfig such as which REST method to use
  2. dispatch - the dispatch which the thunk has access to
  3. endpoint - the endpoint for the API that you want to query
  4. types - the three strings to use for each of the redux actions put into a tuple. They are in an array so sequence is very important - it goes request, success, failure.
// apiHelper.js
const callAPI = async (args) => {
	const { 
		config, 
		dispatch,
		endpoint, 
		types 
	} = args;

	const [request, success, failure] = types;
	const url = `https://jsonplaceholder.typicode.com/${endpoint}`;

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

	dispatch({
		type: request
	});

	const response = await fetch(url, fetchConfig);

	if (response.ok) {
		try { 
			const data = await response.json();
			dispatch({
				type: success,
				payload: {
					data
				}
			});
			return;
		} catch (error) {
			dispatch({
				type: failure
			});
		}
	}

	dispatch({
		type: failure
	});
}

export callAPI;

By introducing the tuple for types, we’re able to very easily re-use this for other actions. Here’s the updated posts actions:

// postsActions.js
const loadPosts = () => async dispatch => {
	callAPI({ 
		config: { method: "GET" },
		dispatch,
		endpoint: "posts",
		types: ["Posts.LOAD_POSTS_REQUEST", "Posts.LOAD_POSTS_SUCCESS", "Posts.LOAD_POSTS_FAILURE"]
	});
}

And the newly added comments actions:

// commentsActions.js

const loadComments = () => async dispatch => {
	callAPI({ 
		config: { method: "GET" },
		dispatch,
		endpoint: "comments",
		types: ["Comments.LOAD_COMMENTS_REQUEST", "Comments.LOAD_COMMENTS_SUCCESS", "Comments.LOAD_COMMENTS_FAILURE"]
	});
}

Up Next:

In the next post we’re going to go deeper with Redux and introduce a middleware to handle calling our API and discuss some of the benefits of this approach.

Published 10 Jan 2019

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