Blog

Redux in Plain English: A Workflow

What’s a Redux?

Redux is an architecture pattern and a library. Its gift to you is the ability to manage your application’s state in a single object. While the Redux documentation is robust and includes links to some great video tutorials and example projects, it can all be overwhelming. The goal of this post is to be an effective primer for all those resources.

Redux was developed by Dan Abramov as one of many alternatives to Facebook’s Flux architecture. Facebook liked it so much, they gave Dan a job. It’s worth noting that Redux (like Flux) can be utilized outside of just React applications, but this article will reference Redux within a React context. This post assumes you have some familiarity with React.

A Note on Smart vs. Dumb Components

React/Redux applications are made up of ‘smart’ and ‘dumb’ components. Dumb components are intended to be passed exactly the data they need to render their piece of the puzzle, and no more. They should be ignorant (‘dumb’) to the rest of the world. In general, any callbacks they need should be passed down to them from another component. Static pages are obvious candidates for dumb components: They likely have no business knowing when state is updated in the rest of the application.

Smart components then, are sensitive to state changes in the rest of the application. They can initiate changes themselves, or can listen for changes produced by other smart components. What this means technically is that smart components have access to the single global state object.

There is a healthy amount of debate in the community about when and how often to use each component. Ultimately the correct answer is whatever works best for your application. A popular pattern is to make a component dumb until its begging to be made smart. What I mean by that is if a dumb component interacts heavily with one or more smart components, and the maintenance of that becomes cumbersome, consider upgrading it to a smart component. 

You will commonly see smart components within a containers directory and containing the suffix Page (i.e. LoginPage). Dumb components are typically within a components directory. There are, of course, several other patterns in the wild. For more on the smart vs. dumb divide, see Dan’s thoughts on the subject.

A Workflow

Let’s walk through an example Redux workflow. Say we have a forum-like website and want to fetch some questions from a server and display them to the user. When a user visits /questions, we’ll have our router point to the QuestionsPage, a smart component.

Make a component smart

The notion is pretty straight-forward: if you want to wisen up your component, use the connect method from react-redux. Connecting your component gives it access to the global state object. It’s highly unlikely however, that our component needs access to everything in the state object. We can slice off only what we want the component to be aware of in the mapStateToProps function. The global state object will have various other keys in it, but in the example below, we’re only allowing questions through.

// /containers/QuestionsPage.js

import { connect } from 'react-redux';

const QuestionsPage = React.createClass({
   ...
});

function mapStateToProps(state) {
   return { questions: state.questions };
}

export default connect(mapStateToProps)(QuestionsPage);

Dispatch an action

Within the QuestionsPage component, we can use one of React’s component callbacks (i.e. componentWillMount) to initiate the fetching of questions. All smart components have access to the dispatch method via props.  The dispatch method allows us to execute an action creator, perform logic, and ultimately update the global state object.

// /containers/QuestionsPage.js

import { fetchQuestions } from '../actions/index';

const QuestionPage = React.createClass({
   ...
   componentWillMount() {
      this.props.dispatch(fetchQuestions());
   },
   ...
});

Perform logic with an action creator

The action creator layer is where we’ll perform all Ajax requests and other internal logic to fetch or prepare data for the reducer. In our case, we’ll fetch some questions from a remote server and send them off to the reducer. This example uses a library called superagent to perform the Ajax request.

Notice below that we’re dispatching requestQuestions prior to making the Ajax call. This is to let our application state know that we’ve made a request, but have not yet received the response. In this state, we can display a spinner/loading gif, for example. Once the response comes in, we parse the body and send the array of questions through to the reducer. The logic behind the type in the return values will become clear in the reducer.

Also notice that we’re exporting the fetchQuestions function, but not requestQuestions or receiveQuestions. As we’ve seen in the previous step, fetchQuestions is imported and called in a component, while the other functions need only be accessible locally.

// /actions/index.js

import { get } from 'superagent';

export function fetchQuestions() {
   return dispatch => {
      dispatch(requestQuestions());
      get('http://example.com/api/v1/questions')
      .type('application/json')
      .accept('application/json')
      .end(function(err, res) {
         try {
            dispatch(receiveQuestions(res.body.questions));
         } catch (e) {
            console.log('GET request failed!');
         }
      });
   };
}

function requestQuestions() {
   return { type: 'REQUEST_QUESTIONS' }
}

function receiveQuestions(questions) {
   return { type: 'RECEIVE_QUESTIONS', questions };
}

The reducer builds the final state object

The reducer’s responsibility is to take the application state, account for changes coming from the action creators, and boil it all down (‘reduce’ it) to the new state. It is important to note that the reducer is a pure function, meaning it does not mutate the existing application state. Instead, it outputs a brand new application state for every change that is made. One of the implications of this is the ability to view and playback a user’s interaction with the application by tracing each change to the applications state. The wonderful Redux DevTools allow you to do just that.

While I’ve been referring to a single reducer, the function is actually the combination of several domain reducers. In this example, there is a reducer for questions and another for auth. To be clear, the latter does not relate to any of the code we’ve already written, it is just to illustrate the point. Each domain reducer is combined using the combineReducers method.

Inside of the questions reducer, we do what’s called “splitting the reducer.” Remember the type value we returned from the action creator? We’ll use a switch statement based on that value to update the state object appropriately. Note that the Object.assign function is a another feature of ES6. It returns a new object, based on the old state, but with updated fields as passed in by the third argument.

// /reducers/index.js

import { combineReducers } from 'redux';

const initialState = {
   fetching: false,
   list: []
}

function questions(state = initialState, action) {
   switch (action.type) {
   case 'REQUEST_QUESTIONS':
      return Object.assign({}, state, {
         fetching: true      
      };
   case 'RECEIVE_QUESTIONS':
      return Object.assign({}, state, {
         fetching: false,
         list: action.questions
      };
   }

   return state;
}

function auth(state = initialStateAuth, action) {
   ...
}

const rootReducer = combineReducers({
   auth,
   questions
});

export default rootReducer;

We come full circle

Once the reducer produces the new state object, those smart components listening in become aware of the changes made and respond accordingly. In memory, our application will run the diff of changes that will need to occur in the DOM, and efficiently update only those lines that need to be.

Returning to the QuestionsPage, our questions are now available to us via this.props.questions.list. From here we could iterate over the array of questions and render a Question dumb component for each.

That’s it! We’ve successfully navigated Redux’s “strict unidirectional data flow.” Actions are dispatched by smart components. Action creators manipulate data and hand off to reducers to compile the new state object. Then smart components catch wind of the changes to state, and the cycle begins anew.

Now what?

Is it all starting to sink in? You’re in a good place now to start digging into some of the details we’ve strategically skipped over. For example, before any of this can work, the Redux store will need to be configured. Check out one implementation here. You might be interested to know that there is a Redux router which treats routing information as part of the global state object as well. Again, the Redux DevTools are amazing. Want more links? Boom.

You’ll also notice that nearly every example application you’ll find is written in ES6 and uses webpack. Webpack is a relatively new and very powerful tool worth your time. If its documentation also falls into the “overwhelming” category for you, try using an example application’s configuration file and tweaking it to fit your needs. Webpack is used to load Babel, which allows us to use ES6 (and beyond) features today.

Now that you have a dozen new bookmarks, lets have a quick chat about expectations. Before you turn into this guy, please know that most of these libraries are still quite young! They absolutely won’t be devoid of all bugs or move at predictably slow paces. Get over it now, or better yet, embrace it by filing issues, contributing, and helping to shape these exciting new technologies.

Now, ready to get moving? I would start here.