Redux State Manager

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));

Building A Sample React-Redux Application

What We’Re Going To Build

Now let’s continue with the pratical 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 every time 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/reducersand 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 Appcomponent 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.

Last updated