In Part 1 of this series, you learned the basics of Redux, saw how to use Redux in stand-alone mode, and began exploring the use of Redux with the React framework. This installment continues that exploration, beginning with a discussion of the React bindings for Redux, which make it possible to separate stateless presentation components from components that are connected to React. Then I’ll switch gears to a more complex application (one that also uses React) to illustrate the more advanced aspects of Redux.

Using the react-redux bindings

Redux bindings are available on GitHub for several popular frameworks, including React, Angular, and Vue. The bindings make it easy to integrate Redux into applications built with those frameworks.

The react-redux bindings give you access to an API that includes a React component and a JavaScript method:

  • The Provider component provides access to the Redux store for components contained in the Provider component.
  • The void connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) function connects a React presentation component to the Redux store.

The Provider component is a convenience so you don’t need to manually pass the store as a property throughout your component hierarchy. Components that you nest inside the Provider component automatically have access to the Redux store.

The connect() function connects a presentation component to the Redux store, so that the component is updated whenever the store changes.

Automatic connection to the Redux store

In Part 1, you saw how to connect individual React components to the Redux store. That idiom is so prevalent that the redux-react bindings provide a connect() method that automatically connects stateless functional components to the store.

To demonstrate, I’ll start by revising the App component for Part 1‘s traffic light application so that it uses what is referred to as container components— components that automatically connect to the Redux store and provide properties to an enclosed stateless functional component. Listing 1 shows the updated App component.

Listing 1. The application (app.js), which now uses container components

import React, { Component } from 'react';
import { StoplightContainer } from './stoplight-container';
import { ButtonContainer } from './button-container';
import { createStore } from 'redux';
import { reducer } from './reducer';

export class App extends Component {
  render() {
    const store = createStore(reducer);

    return(
      <div>
        <StoplightContainer store={store}/>
        <ButtonContainer store={store}/>
      </div>
    )
  }
}

Instead of returning a DIV with the Stoplight and Buttons components, as was the case for the original implementation, the App component’s revised render() method returns a DIV with two container components: StoplightContainer and ButtonContainer.

Notice that I’m still passing the Redux store to the components contained in the App component. You’ll see how to sidestep that requirement in the next section.

Listing 2 shows the stoplight container.

Listing 2. The stoplight container (stoplight-container.js)

import { connect } from 'react-redux';
import { Stoplight } from './stoplight';

const mapStateToProps = state => {
  return {
    goColor:      state == 'GO'      ? 'rgb(39,232,51)' : 'white',
    cautionColor: state == 'CAUTION' ? 'yellow' : 'white',
    stopColor:    state == 'STOP'    ? 'red' : 'white'
  }
}

const mapDispatchToProps = null;

export const StoplightContainer = connect(
    mapStateToProps,
    mapDispatchToProps
)(Stoplight);

The gist of StoplightContainer is that it connects to the Redux store and maps application state to the properties of the stoplight that it contains. The application state is either GO, CAUTION, or STOP, and the stoplight’s properties are goColor, cautionColor, and stopColor.

By default, each of the stoplight’s three properties is white. When the state is GO, the stoplight container maps the stoplight’s goColor property to traffic green (coded as rgb(39,232,51)). When the state is CAUTION, the caution color maps to yellow. When the state is STOP, the stop color maps to red.

To map application state to stoplight properties, Listing 2 uses the react-redux bindings’ connect() function to connect a StoplightContainer to the Redux store.

By calling the connect() function and subsequently passing Stoplight to the function that connect() returns, Redux automatically updates the Stoplight whenever the state in the Redux store changes. You don’t need to do anything other than call connect() to make that happen. When the store changes, Redux calls the StoplightContainer‘s mapStateToProps() method. Redux copies the values of the properties of the object returned by StoplightContainer.mapStateToProps() to the stoplight enclosed in the StoplightContainer.

The connect() method accepts two parameters, both of which are functions. The first function maps state from the Redux store to properties of the contained component (in this case, Stoplight). The second function maps Redux dispatch calls to properties; however, the stoplight doesn’t initiate any behavior, so the stoplight container doesn’t map dispatch calls to properties. As a result, the stoplight container’s mapDispatchToProps function is null.

Listing 3 shows the once-again-revised implementation of the Stoplight component, which uses its three properties as the fill attribute for SVG circles.

Listing 3. Stoplight (stoplight.js), reverted to a stateless functional component

import React, { Component } from 'react';

export const Stoplight = ({
  goColor,
  cautionColor,
  stopColor
}) => {
  return(
    <div style={{textAlign: 'center'}}>
      <svg height='170'>
        <circle cx='145' cy='60' r='15'
                fill={stopColor}
                stroke='black'/>

        <circle cx='145' cy='100' r='15'
                fill={cautionColor}
                stroke='black'/>

        <circle cx='145' cy='140' r='15'
                fill={goColor}
                stroke='black'/>
      </svg>
    </div>
  )
}

The Stoplight component has reverted to a stateless functional component that receives its properties from the StoplightContainer component.

Listing 4 shows the ButtonContainer component.

Listing 4. The button container (button-container.js)

import { connect } from 'react-redux';
import { Buttons } from './buttons';
import { goAction, cautionAction, stopAction } from './actions';

const mapStateToProps = state => {
  return {
    lightStatus: state
  }
}

const mapDispatchToProps = dispatch => {
  return {
    go:      () => { dispatch(goAction) },
    caution: () => { dispatch(cautionAction) },
    stop:    () => { dispatch(stopAction) }
  }
}

export const ButtonContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(Buttons);

The ButtonContainer component maps the current state to a Buttons property named lightStatus. The light status is simply the value of the state (GO, CAUTION, or STOP).

Unlike the Stoplight component, the buttons in the Buttons component do initiate behavior that changes state, so the ButtonContainer maps dispatch() calls to Buttons properties. Those properties are functions that the Buttons component uses, as shown in Listing 5.

Listing 5. The Buttons component (buttons.js), also reverted to a stateless functional component

import React, { Component } from 'react';

export const Buttons = ({
  go,
  caution,
  stop,
  lightStatus
}) => {
  return(
    <div style={{textAlign: 'center'}}>
      <button onClick={go}
              disabled={lightStatus == 'GO' || lightStatus == 'CAUTION'}
              style={{cursor: 'pointer'}}>
        Go
      </button>

      <button onClick={caution}
              disabled={lightStatus == 'CAUTION' || lightStatus == 'STOP'}
              style={{cursor: 'pointer'}}>
        Caution
      </button>

      <button onClick={stop}
              disabled={lightStatus == 'STOP' || lightStatus == 'GO'}
              style={{cursor: 'pointer'}}>
        Stop
      </button>
    </div>
  )
}

The Buttons component in Listing 5 uses its go, caution, and stop properties, which are all functions, as callbacks for each button’s onClick handler. Those properties come from the ButtonContainer component. Notice that — like the Stoplight component — the Buttons component has reverted to a functional stateless component.

Separation of presentation components and container components

Not only do the react-redux bindings provide an automatic connection to the Redux store, but they also help to enforce good programming practice by separating concerns between containers and their associated stateless components. Container components implement mapStateToProps(), which maps state to data; and mapDispatchToProps(), which maps state to behavior. This separation has several benefits:

  • Presentation components are simple to implement and reason about.
  • Presentation components are easy to test, because they don’t mutate data.
  • Presentation components can be reused with different data sources.
  • Container components are easy to test, because they lack presentation code.

So much for the react-redux bindings’ connect() function; now on to the React Provider component.

Redux providers

One pleasant side effect of using Redux’s connect() function is that stateless functional components such as Stoplight and Buttons no longer need to access the Redux store directly — because those components no longer compute their properties from state. Instead, the corresponding container components provide the glue between the Redux store and an application’s stateless functional components. That arrangement makes the stateless functional components, such as the final versions of Stoplight and Button, much easier to test.

However, the container components still access application state to map it to the properties of the container’s enclosed stateless component. To make the Redux store available to the application’s React components, you can explicitly pass it down the component hierarchy, or you can use the Provider, as shown in Listing 6.

Listing 6. Using the Provider component (index.js)

import React from 'react';
import ReactDOM from 'react-dom';
import Redux, { createStore } from 'redux';
import { Provider } from 'react-redux';

import { reducer } from './reducer';
import { App } from './app';

ReactDOM.render(
  <Provider store={createStore(reducer)}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Properties that you specify for Provider become automatically available to any React components contained in the Provider component. In this case, the App component — and the StoplightContainer and ButtonContainer components contained in the App component — automatically have access to the Redux store.

So far you’ve examined Redux fundamentals and the react-redux bindings in light of a simplistic application with the simplest possible state — a single string. To understand more-advanced aspects of Redux, it’s time to turn to a more ambitious undertaking.

The book-search application

You’ve already come a long way toward mastering Redux, but there’s much more ground to cover, including:

  • Implementing and using action creators
  • Combining reducers
  • Creating asynchronous actions
  • Implementing undo and redo
  • Implementing a state timeline

I’ll use the application shown in Figure 1 to illustrate the preceding topics, covering the first one in this installment and the rest later on. The book-search app uses the Google Books REST API to asynchronously search for books. After the user enters a topic into the text field and presses Enter, the application fetches information for the first 10 books that match the topic, displaying a thumbnail of each book’s cover.

Figure 1. The book-search application
Screenshot of search results in the book-search app

The book thumbnails are links. When you click one, you see more information about the book on books.google.com, as shown in Figure 2.

Figure 2. Result of clicking a book thumbnail
Screenshot of book detail page

The application provides an alternative list view, shown in Figure 3. You activate the list view by clicking the List radio button.

Figure 3. The list view
Screenshot of the book-detail list view

Finally, the application supports undo (by clicking the left arrow) and redo (by clicking the right arrow), along with a history slider that moves you back and forth among the application’s previous states.

That’s an overview of the book-search application. Next I’ll walk you through implementing the app using React and Redux.

Components

First, I create a mockup of the application, shown in Figure 4.

Figure 4. Mockup
The application mockup drawing

Next, I come up with a hierarchy of components, shown in Figure 5.

Figure 5. The components for the book search application
Components mockup

The component hierarchy looks like this:

  • App
    • Controls
      • Topic Selector
      • Display Options
      • History
    • Books
      • Book … State viewer

And here’s the directory structure and associated files for the application:


actions.js
  components
    book.css
    book.js
    books.js
    controls.js
    displayModeOptions.js
    history.js
    stateviewer.js
    topicselector.js
  containers
    app.js
    books.js
    controls.js
    history.js
    stateviewr.js
    topicselector.js
images
  app.js
index.html
index.js
middleware.js
node_modules
package.json
reducers.js
statehistory.js
store.js
webpack.config.js

Seven of the application’s eight components have corresponding Redux container components, for a total of 15 components. The container components are in the container directory, and the presentation components are in the components directory.

The application entry point

Figure 6 shows the starting point for the book-search application, consisting of the controls component with the topic selector and the display options.

Figure 6. The starting point for the book-search app
Screenshot of the book-search app's start page

Listing 7 shows the code for the entry point, which renders the App component. That component is wrapped inside a Redux Provider component. Recall that the provider makes the Redux store available to the application.

Listing 7. The entry point (index.js)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { App } from './containers/app';
import { store } from './store';
import { setTopic, setDisplayMode } from './actions';

store.dispatch(setTopic('javascript'));
store.dispatch(setDisplayMode('THUMBNAIL'));

ReactDOM.render(
   <Provider store={store}>
     <App />
   </Provider>,
document.getElementById('example') 
) 

function showState() {
   const state = store.getState();
   debugger
 } 

store.subscribe(showState);

The application’s entry point also subscribes to the Redux store; when the state in the store changes, Redux calls showState(), which gets the application’s state and invokes the debugger.

Before it renders the application and subscribes to the Redux store, the entry point dispatches two actions, one to set the book topic and another to set the display mode.

Unlike the calls to store.dispatch() in the stoplight example from Part 1, the two dispatch calls in Listing 7 use functions —setTopic() and setDisplayMode()— to create action objects. Those functions are known as action creators.

Implementing action creators

Listing 8 shows the implementation of the set(Topic) and setDisplayMode() action creators — functions that take a single parameter and return a corresponding action.

Listing 8. The actions (actions.js)
export const setTopic = topic => {
  return { 
     type: 'SET_TOPIC',
      topic 
   } 
} 

export const setDisplayMode = displayMode => { 
   return { 
     type: 'SET_DISPLAY_MODE', 
     displayMode 
   } 
}

Action creators might seem like a circuitous way to come up with an action object. It would be simpler to specify the actions directly. However, implementing action creators typically in one file, or a handful of files, makes it easy to locate the code for your application’s actions, which in effect serves as a form of documentation.

Conclusion to Part 2

In this installment, you saw how to use the react-redux bindings to automatically connect to the Redux store and to separate container components from their presentation counterparts. Container components connect to the Redux store, and when the store’s state changes, the container components map the current state to properties of their presentation components. Those properties can be data or functions.

You also saw that it’s not necessary to pass the Redux store throughout your component hierarchy. Instead, you can use the Provider component that comes with the react-redux bindings to make the Redux store available to all your components.

Finally, you started learning about advanced Redux features through the book-search example. You saw that you can encapsulate the creation of actions in functions, making your code more readable.

The next installment continues coverage of advanced Redux features via the book-search example application. You’ll see how to deal with more-complicated state by combining reducers, and you’ll start learning how to implement asynchronous actions.