loading...

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

patrickgordon profile image Patrick ・4 min read

original post can be found on my blog

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.

Discussion

pic
Editor guide
Collapse
markerikson profile image
Mark Erikson

FWIW, I recommend against passing in the action creators under a props.actions object, for two reasons:

  • It sorta makes the component "aware" of Redux when you're specifically labeling those functions as "actions"
  • More specifically, it means you have to actually write a mapDispatch function instead of using the "object shorthand" for binding the action creators. See our new React-Redux docs page on using mapDispatch for more details on the various ways this can work, including the "object shorthand" that we recommend.
Collapse
patrickgordon profile image
Patrick Author

Hey Mark,

Thanks for the interesting comment - I actually wasn't aware of the new object shorthand. That looks really interesting.

I'd be curious to hear your thoughts more about why it matters if the component is 'aware' of redux. I would say the main reason we use the props.actions object is just for consistency and tidiness.

Cheers for engaging, I appreciate it!

Collapse
markerikson profile image
Mark Erikson

Thing is, the "object shorthand" isn't new at all - it's been there since day 1 :) See react-redux issue #1 for early discussion. It's even in the existing API docs, but it's kinda buried in there, and you have to squint hard at the right paragraph to understand it.

The "awareness" thing is mostly a general principle. I think it's worth trying to keep most components generic and reusable in the sense that they shouldn't care where their props are coming from. It's not always achievable, but it's a useful long-term architectural objective.

Thread Thread
patrickgordon profile image
Patrick Author

Well, you learn something new every day!

Thanks for the insight, Mark!