I recently did a technical test using ReactJs and made a conscious decision to use the new Context API to share some data between components. I have to admit it felt natural and light especially coming from a previous project where I used vanilla Flux. There are several reasons why I made this decision:

  1. I wanted to see how well the new Context API could share common data;
  2. I have never really spent much time with Redux;
  3. Redux is not always required depending on the solution you are trying to solve;
  4. I was under a time constraint.

I really enjoyed using Context API however, given the popularity of Redux I wanted to revisit it for another application I am currently working on to see what is involved and what differences there was from previous experiences.

What is Redux?

Redux is:

A predictable state container for JavaScript apps

What does this actually mean; in short, it creates a predictable application by following three core principles.

  1. Single Source Of Truth;
  2. State is read only;
  3. Changes are made by pure functions.

The full explanation of these principles are on the Redux website here. However in short what this means is we have one store that contains all the data our application needs to hold. The only way we can update the store is by emitting an action and the changes to the store are completed using pure functions known as reducers. A pure function will return the same result every time given the same arguments. There can be no side effects in a pure function, a side effect could be an API call or even as simple as mutating a variable.

There are good resources online going into this in more detail, but I have highlighted the three pillars that hold Redux together.

This article is a 5-minute tour of how I have implemented Redux and retrieved some data from an API that can then be saved to the store.

Overview of Redux

Setting up the store

Redux is designed to work with any front end technology, but for this article I will be using it with React. This requires two libraries required to be imported, react-redux and react and can be installed using npm or yarn.

Once installed the first thing I did was configure a store that is used with my application. The store can be described as a central warehouse for the data in your application. This allows any component can subscribe to the store to be notified of any data. A good place to set the store up is at the entry point of the application, in my case index.js.

The following snippet shows how I am creating a store. I am passing a reducer into the store to let the store know what will be updating the contents and applying a middleware called thunk.

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. We will see why this is required later.

In my example below I dispatch an action to ensure my store contains an access_token required for my application.

The final interesting part is I am using the <Provider /> component to wrap my application and passing the store to it. This allows me later on to use the connect() function that will allow me to access store in my React components.

const store = createStore(rootReducer,applyMiddleware(thunk));
store.dispatch(updateAccessToken(localStorage.getItem("access_token")));
 
ReactDOM.render(
<Provider store={store}><App /></Provider>, document.getElementById('root'));

Creating Reducers

When I have created the store I am required to pass it a reducer. This is required as the first parameter. The reducer is responsible for receiving an action and updating the store. The reducers should be pure which means they are not to modify the store values directly; rather they should return a modified state. Using the code snippet below I will explain how to set up a reducer.

const INITIAL_STATE = {
    athlete: {
        isLoading: false,
        error: false,
        firstname: '',
        surname: ''
    },
    application: {
        access_token: undefined,
        expires_at: undefined
    },
    auth: {
        loggedIn: false
    }
};
 
export const rootReducer = (state = INITIAL_STATE, action) => {
    switch (action.type){
        case 'SAVE_ATHLETE_INFO': {
            return {
                ...state,
                athlete: {
                    ...state.athlete,
                    firstname: action.value.firstname,
                    surname: action.value.lastname
                }
            }
        }
 default:
            return state;
        }        
    }

The reducer is a pure function that is simple as:

(previousState, action) => newState

This would obviously not work on the first start of an application because previousState would be undefined. Therefore, it is required to pass an initial state to the store. The previousState can be anything but it makes sense to give it the shape of the data you intent to hold. In my example above, INITIAL_STATE contains the shape of the state I would like to store.

The reducers job is to handle actions and return a new state. The first task required is to check the action.type. In the example, when an action with a type of ‘SAVE_ATHLETE_INFO’ is received we want to do something. In this case, we are going to save the firstname and surname of an athlete. However, you can see I am doing more than just setting the name.

Since this is a pure function we cannot modify state directly, instead we need to take the previous state and return a new state.

  • Using the spread operator we copy the existing state into a new object (…state).
  • The second step we do is overwrite the athlete object and again first need to copy the previous athlete state (…state.athlete).
  • The third step required is to overwrite firstname and surname with the values received in the payload.

The above steps ensure that we do not miss any of the structure from our data’s shape. If we missed out the first copy of state, then the object we would return simply would contain the athlete object.

Actions

The second parameter of a reducer is the action it receives. An action is simply a plain JavaScript object that contains a type and optional payload. In my example, I supply a type and a value and the value contains athlete information retrieved from an API. I have action creators that returns the specified action. Using action creators saves the need to write the action out multiple times and helps reduce any potential errors.

export function saveAthleteInfo(athleteData) {
    return {type: ‘SAVE_ATHLETE_INFO, value: athleteData };
}

In order to dispatch an action to the reducer I can invoke this by calling the dispatch method on the store. For example:

store.dispatch(saveAthleteInfo(result.athleteData));

Connecting Redux to React

So far, we have created a store, a reducer and an action. This all needs connected to our React components. React-redux supplies a connect() function that is a function that returns a Higher Order Component which can wrap around our React component. The connect function accepts two methods:

  • mapStateToProps(state, ownProps?);
  • mapDispatchToProps(dispatch).

mapStateToProps allows you to map values in the state to the props for the component to display data. mapDispatchToProps allows you to access dispatch methods to props for the component. Combined they allow you to display data and perform actions to trigger updates to the store. In my example code, I have a component that is responsible for displaying the athlete’s name. To achieve this I need to connect it to Redux and I have written two functions to mapStateToProps and mapDispatchToProps:

function mapStateToProps(state) {
    return {
        access_token: state.application.access_token,
        isLoading: state.athlete.isLoading,
        error: state.athlete.error,
        athlete: state.athlete
    };
}
 
function mapDispatchToProps(dispatch) {
    return {
        getAthlete: token => dispatch(getUserProfile(Endpoints.GET_ATHLETE, token))
    };
}

In the mapStateToProps function, I return an object that sets four props to values that I retrieve from the store state and make them accessible to my component. In mapDispatchToProps I return an object that contains a prop called getAthlete. getAthlete is a function that accepts a token and dispatches an action to get the data.

We can use these functions by passing them through to the connect method as follows:

/* … */ 

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

Middleware - Thunks

When I first heard about thunks I was confused. I did not really know what the term meant but also what its purpose is. It turns out that thunks are straightforward and actually pretty useful. A thunk is a name for a function that returns another function; simply put it is a fancy name for a wrapper function. The question is, what is so useful about them? So far, we have learned about stores, actions and reducers. Where can we call an API and store the data?

It cannot be in the reducers because side effects are prohibited – remember an API call is a side effect.

The only logical place to put them is to create an action creator that can call an API. However, actions are simply plain objects that contain no logic.

react-thunk is middleware that intercepts every action and if the action is a function it calls it. We can create an action creator that returns a new function that does some work and then dispatches an action. The following code is a thunk used to retrieve some data from an API then dispatches it with the results as payload that can be saved in the store.

export function getUserProfile(resource,token) {
   return async (dispatch) => {
    try {
        dispatch(SetIsLoading(true));
        let result = await stravaService.doGetDataItem(resource,token);
        dispatch(saveAthleteInfo(result.athleteData));
        dispatch(SetIsLoading(false));
      } 
      catch (error)       
      {
         console.log(error);
      }
   }
}

That is it really; once the penny dropped Redux makes a lot more sense than it had before. I will see how it goes as I grow out the application but it does feel a lot more controlled to know that my data is in a central location and updated in a predictable way.