(4) An easy framework for thinking in Redux (and setting up for Redux Thunk)
By: David Lavieri
Tags: JavaScript, Redux Thunk, React, Redux, Front End Development

The concept of Redux is not so complicated, and in an abstract way if you’ve made a very deeply nestled React application where all your state is held in <App/> and then flows endlessly through your hierarchy of components, it works similar to that but in a far more simplified architecture. Redux centralizes your state in something called a “store” but instead of cascading down levels of components, you’re able to inject state (and dispatches — function props that impact state) directly into the components that need them.

This enables two major benefits. (1) It is way easier to make changes, like changing the keyname for a piece of state. You no longer need to change it on every level of the component architecture, only where it is explicitly used. (2) It will make your application faster, because rather than re-rendering the whole branch of components, starting from App (triggering prop re-renders), it will only re-render the actually changed component.

Where Redux starts to feel a bit foreign is in implementation, especially if dealing with something like Redux-Saga there can be considerable boilerplate code. And if you’re working in a very simple application (like a to-do list app, courtesy of the Redux documentation example) you might be thinking that it’s alot of work to do something that’s simple in React — and it is! For small and non-hierarchical single page apps, Redux might not be a tool that you need to use. But for a larger scale application, with deep branches of components and/or multiple routed pages, in the end you will find that after setup, the process of managing your app is much simplified.

So, how does Redux work? Basically there are three cogs in the redux machine: the store which holds our application’s state, actions which define state mutations, and the reducer(s) which map actions to new state objects (note that we still will never directly mutate state).

OK that sounds simple enough, how do we set it up? Well here’s an easy step-by-step process for setting up your Redux architecture:

(1) Set up the store

Personally, I like to keep all of my redux logic in a redux folder (and subsequently actions and reducers folders). So in that folder I create a store.js file. In there, all we need is:

import { createStore, applyMiddleware, compose } from 'redux';

import thunk from 'redux-thunk';

import logger from 'redux-logger';

import rootReducer from './reducers'; const composedEnhancers = compose(applyMiddleware(thunk, logger)); const store = createStore(rootReducer, composedEnhancers); export default store;

That’s it for the store! Here I am applying a couple middleware, but if you’re just making a simple redux app (without thunk or logger), then you won’t need to compose any Enhancers.

From top to bottom, we are importing our functions from Redux ( createStore, applyMiddleware, compose ), and also our Reducer from our reducers file (which we will create in step four — don’t fret! It may be advisable at this stage to set up an empty rootReducer function and export it). Then we “compose” our middlewares by applying the two middlewares we will be using. The createStore function takes two args: our reducer function, and our composed middlewares.

Lastly we export.

(2) Wrap our App in a Redux Provider

Remember how I said that Redux is a lot like passing state from a higher order component? Well here’s that higher order component:

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import App from './App';

import * as serviceWorker from './serviceWorker';

import store from './redux/store';

import { Provider } from 'react-redux'; ReactDOM.render(

<Provider store={store}>

<App />

</Provider>, document.getElementById('root'));

We import the store from our store.js file, and import Provider class from Redux. Then we wrap our <App/> in the Provider component, and pass it the store as a prop.

(3) Define your actions

Actions (and action creators) are a kind of strange piece of the Redux ecosystem. They are objects that hold all the information that our reducer will need to create a new state. Together you could think of them as your handler functions (kind of). The actions will be the name of your handler, which you store in a variable, for example:

export const LOGIN = "LOGIN";

Seems a little stupid right? Well by storing it in a variable you are centralizing the points of failure. If you want to change the name, you have only one variable to change rather than tracking down all the instances of it. Convention holds that actions are all-capitalized and underscored.

Ok, so now we have a useless variable… how does that effect state? With an action creator! Action creators are just functions that return objects that have all the information you need to change state. Sometimes for toggling booleans, for example, maybe they only include the action, but for others they will pass data. For the login action creator:

export function login(user, token) {

return {

type: LOGIN,

user: user,

token: token

}

};

The login action creator will dispatch the user and access token data, along with the action (“type” of action, in the creator) to the reducer.

(4) Setting up our rootReducer and initialState

Ok, the moment of truth: the reducer! This is where the magic happens in redux. The reducer is a little like a mini server, it is composed of a switch function that takes the action.type and interprets it like a route, to direct it to the right case which contains the logic for actually returning the new state.

import { LOGIN, LOGOUT } from './actions/auth-actions'; const initialState = {

isLoggedIn: false,

user: null,

token: null,

otherState: null

}; const rootReducer = (state = initialState, action) => {

switch(action.type) {

case LOGIN:

return {

...state,

isLoggedIn: true,

token: action.token,

user: action.user,

};

case LOGOUT:

return {

...state,

isLoggedIn: false,

token: null,

user: null

};

default:

return {

...state

}

}

} export default rootReducer;

This is of course a very simplified reducer that will only handle Login and Logout — in my big app and yours this will have much more state to manage. I threw in an “otherState” key to illustrate how you will want to spread state.

First we will import our Actions (not action creators). These will be interpreted to define how we route actions.

The we will define our initialState, just like how you would in a React component, give all your pieces of state a key, and an initial value.

The rootReducer (you could also have a few reducers that you would then combine with help of a Redux method) will hold the logic associated with each action, and it will take two inputs. The first argument is state, which we will default equal to initialState. The second is an action. When we dispatch our actions they will be sent as arguments to the reducer.

Our reducer is a switch function, and it will determine which route to switch based on action.type — which is how we keyed the action name in our creators. If the action.type of LOGIN comes in, our reducer will push it to our first case.

In each case, we will return a new object which will replace the state object in our store. First we want to spread the keys of our state object with …state. Any piece of state that we change will overwrite that spread state, but we don’t want to have to manually include every single key and value for our state object… Next we list the keys and new values that we want changed in the updated state.

Make sure you also include a default case which simply returns the previous state!

(5) Connect your components as needed

Whichever component needs access to the redux store and/or actionCreators, you will import at the top:

import { bindActionCreators } from 'redux';

import { login } from '../../redux/actions/auth-actions';

import { connect } from 'react-redux';

bindActionCreators and the action creator function will only be needed if you need to perform that action from the component, but every connected component will need the connect function, even if it’s just receiving state. By connecting your component you make an explicit link between your UI components and your redux store.

We will also need to map the state object that we receive from the store to the props of that component, which we will need to define in two different functions:

const mapStateToProps = state => {

return {

user: state.user,

token: state.token,

isLoggedIn: state.isLoggedIn,

}

}; const mapDispatchToProps = dispatch => bindActionCreators({

login: login

}, dispatch);

Mapping state to props is easiest to understand. You are making a function that takes state (from the store) as an argument, and defining a new object (and objects that will be combined with your props object) where you name the keys you will be expecting as props, where the values are keys from your store’s state object.

Mapping dispatch is a little more abstract, since you will be using a piece of the redux interface (dispatch and bindActionCreators) to do the work. You’re creating an object that will take the dispatch function (which actually sends actions to the reducer) and feeding it to bindActionCreators, which itself takes two arguments: an object of action creators keyed to their prop names, and the dispatch function.

This part may seem a little abstract because so much of the work is done behind the scenes within what you’ve set up in the redux boiler plate code, but you’ll adapt quickly!

Lastly, you need to connect your component to these two functions. And the connect function does exactly that:

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

The connect function takes these two functions. Order matters! if you don’t need to pass any State, then you will do: connect(null, mapDispatchToProps). If you don’t need to pass dispatch, then you can just pass one argument, which will be mapStateToProps. The function that you create through connect() will then be called immediately and will take your Component as an argument — and all of that will be exported!

Phew you’re set up!

(6) What about thunk?

What if you need to set state from an API call? Since these calls are asynchronous, vanilla Redux can’t manage them, so you will need to do use a middleware. redux-thunk, redux-promise, redux-saga — you have multiple options, but, to me, Thunk is the easiest to use. And for small or medium size applications it seems to work very well.

Thunk will return a new dispatch function, which will enable asynchronous axios calls. It’s that simple!

One API call that I will be to load a Mood playlist. Here is the thunk function:

import { fetchMoodPending, fetchMoodSuccess, fetchMoodError } from './actions';

import axios from 'axios';

import apiPath from '../../App'; function fetchMood(moodId, token) {

return dispatch => {

dispatch(fetchMoodPending());

axios.get(apiPath + moodId, {headers: {Authorization: "Bearer: " + token}})

.then(res => {

if (res.error) {

throw(res.error)

}

dispatch(fetchMoodSuccess(res.data.name, res.data.songs));

return res.data;

})

.catch(err => {

dispatch(fetchMoodError(err));

});

}

} export default fetchMood;

OK, so for API calls it is conventional to make multiple actions, related to the state of that call. These include “fetching” to indicate that the call is being made, “success” which indicates that it has been made successfully, or “error”/ “failure” to indicate… failure. This can be great if you are making a big call and want some loading animation, for example, to run while the state of “fetching” is true, or to trigger your front end Error handling if “error” comes back.

We are declaring a function which will take the mood ID (and for authentication reasons, an access token). All it does is return a new anonymous function which takes redux’s dispatch function as it’s argument.

The first thing we do is dispatch our fetching action creator, which is a function called within the dispatch function. This will update our state to show that the API call is being fetched. Next we make our axios call to our API, which is an asynchronous function, and we attach our Authorization header to the axios call since this is a private route on our backend.

If we receive data from our API, it will trigger the success action creator to be called, which will take our received data as arguments and update our state accordingly. If our API call fails, it will return an error and call the error action creator.

And that’s it!

Go forth and reduxify your big ol’ app!