React
Posted By Sebastian

Learn Redux – Introduction To State Management With React


DemoCode

Redux is a predictable state container for JavaScript and comes as an external library which can be used together with front-end frameworks like React, Angular, Ember.js, Backbone etc. However Redux is most often used together with React.

The Problem With Traditional MVC Architecture

Before diving deeper into the concepts of Redux let’s first focus on the problems it solves. The Model-View-Controller (MVC) pattern is familar to most front-end web developers nowadays. This pattern describes a separation between the data (model), the presentation (view) and the application logic (controller). This ensures that your application is build in a structured way and that you achieve a separation of concerns.

However the disadvantage is that you’re loosing control of your data flow. In general the data flow is bi-directional. The user input in one component can affect other components and vice versa. Controlling the flow of data and making sure that all user interface components update accordingly is an error-prone task.

How State Management Solves The Problem

By using Redux we’re solving this problems by introducing a central data store in our application. The store contains the state of the application and is the source of truth for components. By using the store concept you do not need to synchronize state between components manually. Instead you can fully rely on the Redux store at any time.

Building Blocks Of Redux

To fully understand the Redux concept we first need to take a look at the man building block. Redux has three main parts: Actions, Reducers and Store. Let’s explore what each one does:

Actions

Actions are used to send information from the application to the store. Sending information to the store is needed to change the application state after a user interaction, internal events or API calls.

Actions are JavaScript objects as you can see in the following example:

{
    type: LOGIN_USER,
    payload: {username: ‘sebastian’, password: ‘123456’}
}

Here the action object has two properties:

  • type: a constant to identify the type of action
  • payload: the object which is assigned to this property contains the data which are sent to the store

Action objects are created by using functions. These functions are called action creators:

function authUser(data) {
    return {
        type: LOGIN_USER,
        payload: data
    }
}

Here you can see that the only purpose of an action creator function is to return the action object as described.

Calling actions in the application is easy by using the dispatch method:

dispatch(authUser(data));

Reducers

Reducers are the most important building block and it’s important to understand the concept. Reducers are pure JavaScript functions that take the current application state and an action object and return a new application state:

function myReducer (state , action)  {
  switch (action.type) {
    case 'LOGIN_USER':
      return Object.assign({}, state, {
        auth: action.payload
      })
    default:
      return state
  }
}

The important thing to notice here is that the state is not changed directly. Instead a new state object (based on the old state) is created and the update is done to the new state.

Store

The store is the central objects that holds the state of the application. The store is created by using the createStore method from the Redux library:

import { createStore } from ‘redux’;
let store = createStore(myReducer);

You need to pass in the reducer function as a parameter. Now you’re ready to disptach a action to the store which is handled by the reducer:

let authData = {username: ‘sebastian’, password: ‘123456’};
store.dispatch(authUser(authData));

The following figure shows the data flow between the building blocks:

Building A Sample React-Redux Application

What We’Re Going To Build

Now let’s continue with the practical part of this tutorial and start building a real-world React-Redux application from scratch. In the following screenshot you can see what we’re going to build:

The application is giving the user the option to vote for his favorite front-end framework by clicking on the logos. The vote count is stored in the application store and updated everytime the user votes.

The voting result is displayed below by a separate React component. The component accesses to store to get the current voting values for each framework.

Let’s get started!

Setting Up The React Project

First we need to setup a new React project. The easiest way to do so is to use create-react-app:

$ create-react-app my-redux-app

Enter the newly created directory my-redux-app and start the development web server:

$ cd my-redux-app
$ yarn start

Finally we need to make sure that Redux is added to our project:

$ yarn add redux

Implementing Actions

Let’s start the implementation by creating a new file index.js in folder src/actions. This file contains the action creator functions which are needed in our application:

export const voteAngular = () => {
  return {
    type: 'VOTE_ANGULAR'
  }
}

export const voteReact = () => {
  return {
    type: 'VOTE_REACT'
  }
}

export const voteVuejs = () => {
  return {
    type: 'VOTE_VUEJS'
  }
}

The user is able to choose from the list of three front-end frameworks: Angular, React, Vue.js. We’re defining three corresponding action creators here. The action object which is created in all three cases is just containing the type property with one of the following types:

  • VOTE_ANGULAR
  • VOTE_REACT
  • VOTE_VUEJS

Using a payload object is not necessary in this case because we do not need to pass data to the store. We only need the information that a user has voted to increment the vote counter in the store accordingly.

Implementing Reducers

Next, let’s implement the Reducer function. Create a new folder src/reducers and within that folder create the new file index.js and insert the following code:

const initialState = {
  angular: 0,
  react: 0,
  vuejs: 0
}

export default (state = initialState, action) => {
  switch (action.type) {
    case 'VOTE_ANGULAR':
      console.log("Your choice is Angular!")
      return Object.assign({}, state, {
        angular: state.angular + 1
      })
    case 'VOTE_REACT':
      console.log("Your choice is React!")
      return Object.assign({}, state, {
        react: state.react + 1
      })
    case 'VOTE_VUEJS':
      console.log("Your choice is Vue.js")
      return Object.assign({}, state, {
        vuejs: state.vuejs + 1
      })
    default:
      return state
  }
}

First we’re defining a const object which contains the initial state of our application. The state object consists of three properties angular, react and vuejs. Initially the values of these properties are set to 0.

To set the initial state we need to assign the initialState object to the first parameter state of the reducer function as a default value.

The Reducer function contains a switch statement which handles three cases:

  • VOTE_ANGULAR: If an action of type VOTE_ANGULAR has been dispatched to the store a new state object is created and the state property angular is being incremented.
  • VOTE_REACT: If an action of type VOTE_REACT has been dispatched to the store a new state object is created and the state property react is being incremented.
  • VOTE_VUEJS: If an action of type VOTE_VUEJS has been dispatched to the store a new state object is created and the state property vuejs is being incremented.

In each case the Object.assign method is used to create a new state object.

Creating The Store

To complete the Redux parts of our applications, let’s create the store in file index.js:

import { createStore } from 'redux';
import myApp from './reducers';

let store = createStore(myApp);

function render() {
  ReactDOM.render(
    [...]
  );
}

store.subscribe(render);

render();

First we’re making sure that createStore is imported. Furthermore we’re importing the Reducer. Then the store is created by calling createStore and passing over the Reducer as a parameter.

Finally we need to call store.subscribe(render). This makes sure that the render function is called whenever the state of the application changes.

Implementing App Component

Now that we’ve implemented the main Redux building block in our application we need to implement the needed React components as well. We’re starting out by changing the default implementation of App component to the following:

import React, { Component } from 'react';
import { voteAngular, voteReact, voteVuejs } from './actions'
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.store = this.props.store;
  }

  handleVoteAngular = () => {
    this.store.dispatch(voteAngular());
  }

  handleVoteReact = () => {
    this.store.dispatch(voteReact());
  }

  handleVoteVuejs = () => {
    this.store.dispatch(voteVuejs());
  }

  render() {
    return (
      <div>
        <div className="jumbotron" style={{'textAlign': 'center'}}>
          <img src="ctsw_logo.png" height="96" alt="CodingTheSmartWay.com"></img>
          <h2>What is your favorite front-end development framework 2017?</h2>
          <h4>Click on the logos below to vote!</h4>
          <br />
          <div className="row">
            <div className="col-xs-offset-3 col-xs-2">
              <img src="angular_logo.png" height="96" alt="Angular" onClick={this.handleVoteAngular}></img>
            </div>
            <div className="col-xs-2">
              <img src="react_logo.png" height="96" alt="React" onClick={this.handleVoteReact}></img>
            </div>
            <div className="col-xs-2">
              <img src="vuejs_logo.png" height="96" alt="Vue.js" onClick={this.handleVoteVuejs}></img>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default App;

As you can see we’re making use of some Bootstrap CSS classes in the JSX code. A jumbotron is used as the main element which contains headlines and logos. For each logo we’re using the onClick attribute to connect an event handler method to the click event of the image.

The three event handler methods handleVoteAngular, handleVoteReact and handleVoteVuejs are implemented as well. The only task which needs to be performed within the event handler methods is to dispatch the corresponding action to the store. To get access to the application store, the store object is passed to the component as a property:

function render() {
  ReactDOM.render(
    <div className="container">
      <App store={store}/>
    </div>,
    document.getElementById('root')
  );
}

Implement Results Component

The display of the voting results is handled by another component: Results. Let’s create a new file results.js in folder src/components. The source code can be seen in the following listing:

import React, { Component } from 'react';

class Results extends Component {
  constructor(props) {
    super(props);
    this.store = this.props.store;
  }

  votesAngularInPercent() {
    if (this.store.getState().angular) {
      return (this.store.getState().angular / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }

  votesReactInPercent() {
    if (this.store.getState().react) {
      return (this.store.getState().react / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }

  votesVuejsInPercent() {
    if (this.store.getState().vuejs) {
      return (this.store.getState().vuejs / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }

  votesAngularInPercentStyle() {
    return {
      width: this.votesAngularInPercent()+'%'
    }
  }

  votesReactInPercentStyle() {
    return {
      width: this.votesReactInPercent()+'%'
    }
  }

  votesVuejsInPercentStyle() {
    return {
      width: this.votesVuejsInPercent()+'%'
    }
  }

  render() {
    return (
      <div>
        <span className="label label-danger">Angular: {this.votesAngularInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-danger" style={this.votesAngularInPercentStyle()}></div>
        </div>
        <span className="label label-info">React: {this.votesReactInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-info" style={this.votesReactInPercentStyle()}></div>
        </div>
        <span className="label label-success">Vue.js: {this.votesVuejsInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-success" style={this.votesVuejsInPercentStyle()}></div>
        </div>
      </div>
    )
  }
}

export default Results;

Again, the store is passed into the component as a property. The constructor is used to make the store available via this.store.

In order to express the current voting results in percentage three helper methods are defined: votesAngularInPercent, votesReactInPercent and votesVuejsInPercent.

Three additional helper methods (votesAngularInPercentStyle, votesReactInPercentStyle and votesVuejsInPercentStyle) are defined to return the CSS width value for the current voting result. This is used in the JSX code to set the style value of the progress bar element.

The include the output of the Results component in our application make the following changes to index.js:

[...]
import Results from './components/results';


let store = createStore(myApp);

function render() {
  ReactDOM.render(
    <div className="container">
      <App store={store}/>
      <hr />
      <Results store={store}/>
    </div>,
    document.getElementById('root')
  );
}

store.subscribe(render);

render();

First the import statements for Results is added and second the element <Results store={store}/> is added to the JSX code inside the render function.

Having made these changes, you should see the final result of our voting application in your browser.

Conclusion

If you starting out with Redux you might get the impression that more code is needed and that the complexity of your application is growing. This might be true at first but Redux will unleash it’s power when your application grows bigger and consists of a lot of components.

Redux will help you to keep a consistent and clear structure in your project, it will ensure a clear data flow and makes sure that component’s stay in sync.

ONLINE COURSE: The Complete React Web App Developer Course

Check out the great Complete React Web App Developer Course by Andrew Mead with thousands of students already enrolled:

The Complete React Web App Developer Course

  • You’ll learn how to develop, test, and deploy React web applications
  • Learn how to setup and automate testing using Karma and Mocha
  • Understand the rich ecosystem of 3rd-party libraries like Redux and Webpack
  • Learn to style applications using the Foundation framework

Go To Course


Using and writing about best practices and latest technologies in web design & development is my passion.

    View Comments
    There are currently no comments.

    *