DEV Community

Cover image for State Management Using Redux with React
Alexei Dulub
Alexei Dulub

Posted on

State Management Using Redux with React

Redux is used by many for streamlining modern front-end applications. It was designed by Dan Abramov who believed state management to be the key in providing the best user experience and eliminating bugs following through to deployment.

State management is certainly the hardest and challenging aspect of the modern front-end application. Yet, Redux can ensure a compact, mature, and stable solution to state management in your React application. Redux is a useful pattern that is quite capable of transforming your web app into an organized, pleasing, and easily understandable modern powerhouse made using JavaScript.

The Redux principles are packaged and presented in a user-friendly Flux library which gives you more advanced tools for developing JavaScript UIs. Redux can be considered the best framework for the state management of React.js library.

While developing a single page application, the client-side data management proves to be quite complex. As the application gets larger, variations in data, changes in module states, and updated views start showing up unexpectedly. To overcome this size trap, developers at Facebook built Flux as the state management pattern.

Flux complements the composable view components of React by exploiting the unidirectional data drift. The users interact with the views, which then perform actions via a centralized dispatcher, delegating them to stores that hold the logic and data of the application. This updates each view that is affected. Flux comes with a lot of stores and each of them makes use of small chunks of data or state related to the application which brings each module up with a store.

Data Flow in Flux

The user interacts with a view, triggering an action that dispatches a matching function leading to a change in the store. Following this change, the views of each subscriber are updated accordingly, eliminating the need for manual modification of data in various modules.

This data flow is unidirectional and as the app grows, the number of stores that perform data management also grows leading to the increased complexity of the app. Flux offers multiple stores as a solution to this problem.

Redux for JavaScript State Management

Javascript apps have a predictable state container in the form of Redux which has a unidirectional flow different from that of Flux as Flux uses numerous stores, whereas Redux needs only one. The stores are divided into several state objects and it is only required to manage a single store through which the details are fetched.

The Three Principles of Redux are:

  1. There is a single source of truth,
  2. The states are read-only,
  3. The changes are done through pure functions.

The application state is saved in one object inside a store that can only be changed by an action. Pure reducers describe how an action changes the state.

Actions

Actions work as information payloads that transfer data from application to the store using store.dispatch().

Actions are JavaScript objects which are defined by the type of their properties set out using string constants like in the following example:

import { ADD_TODOLIST, REMOVE_TODOLIST } from '../actionType'

Functions serve as action creators:

function addTodoLIST(textdata) {
  return {
    type: ADD_TODOLIST,
    textdata
  }
}

Action Handling Reducers

Actions can only describe an event without providing significant details. A reducer must always remain pure. The reducer is the function passed to the Array.prototype.reduce(reducer,?initialValue). Further, the actions like mutation of reducer arguments, Committing tasks like API calls, Database calls, and routing transitions shouldn’t be performed within a reducer. Also, a non-pure function like Math.random() shouldn’t be called.

Store

This is the object that brings it all together, as it:

  • Holds the application state
  • Provides access to the state via getstate()
  • Allows updating the state via action(dispatch)
  • Allows to register as a subscriber or listener and unsubscribe as well

                                                                                                                                                                                                                                                                                                                                                                                                                                                The Redux app has only one store, so to split the data handling logic, reducers need to be combined.
    

In order to create a store, run the following command:

import { createStore } from redux
import todoApoList from './reducers'
let store = createStore(todoAppList)

Create a project folder and within that make a package.json file. Following that, paste the code given below to that file.

{
  "name": "Countapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "author": "Your Name",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.24.0",
    "babel-loader": "^6.4.1",
    "babel-preset-es2015": "^6.24.0",
    "babel-preset-react": "^6.23.0",
    "babel-preset-stage-3": "^6.22.0",
    "webpack": "^2.3.2",
    "webpack-dev-server": "^2.4.2"
  },
  "dependencies": {
    "react": "^15.4.2",
    "react-dom": "^15.4.2",
    "react-redux": "^5.0.6",
    "redux": "^3.7.2"
  }
}

In the terminal, type: npm install and make a webpack.config.js file within the root folder. The next step will be to create the webpack.config.js file in the root folder.

module.exports = {
    module.exports = {
    entry: main.js file path,
    output: {
        filename: mention within single quote bundle.js here
    },
    module: {
        loaders: [
            {
                loader: this is to be babel-0loader,
                test:/\.js/,
                exclude: /node_module/
            }
        ]
    },
    devServer: {
        port: 9000
    }
};

Create another file known as babelrc:

{
  "presets: ["es2015", "react", "stage-3"
}

Now create the index.html file inside the root:

<!DOCTYPE html>
<html>
  <head>
    <meta charset= "utf-8">
    <title>A Simple Real Life App</title>
  </head>
  <body>
    <div id= "root"></div>
  </body>
</html>

In the terminal, type npm start To run the server with the URL http://localhost:9000

Now we can work with Redux within main.js. Let’s set up the react dependency, as well as react-redux. React-redux delivers a store that exists through the whole application. The Provider wraps the App element:

Import React from 'react';
Import {render} from 'react-dom';
Import {Provider} from 'react-redux';
Import App from './components/App';
render {
<Provider>
    <App />
  </Provider>,
  document.getElementById('root') 

Creating the Components Directory Inside SRC

Create a directory containing the App.js, which is a very essential component:

//App.js 
import React from 'react';
const App = () => {
 <div className="container">
   Various app components
</div>
}
export default App;

App.js is a counter application that includes the other components, the number of which needs to be determined. Whereas React and Redux contain only two components namely Smart and Dumb.

Smart Components

It’s a component that intermingles with the application state directly to access the store, dispatch the actions, and get the application's current state. It is called smart because it updates its state with changes in the store, and transforms the views accordingly. The three smart components are:

    1.     Counter.js


    2.  RemoveCounter.js


    3.  AddCounter.js

These are in the container folder, which will be developed a little later.

Dumb Components

App.js has a child component that never interacts with the store and is kept inside the component folder. We need to create a directory within the SRC with three container components as mentioned earlier. Following that, the AddCounter.js component will be created.

// AddComponent.js
import React, {Component } from 'react';
import {connect } from 'react-redux';
import { appendCounter } from '../actions';
import { bindActionCreators } from 'redux';
class AppendCounter extends Component {
  constructor(props) {
        super(props);
   }
   render() {
     return (
           <div className="container">
            <form>
              <div>
                <div>
                  <button 
                    onClick={(b) => {b.preventDefault();this.props.dispatch(appendCounter())}}>
                      Add
                  </button>
                </div>
              </div>
            </form>
            </div>
     )
   }
}
function mapDispatchwithProps(dispatch) {
  return { actions: bindActionCreators(incrementCounter, dispatch) }
}
export default connect(mapDispatchwithProps)(AppendCounter);

The AppendCounter component is now connected to the redux store. When the user clicks the button, it will dispatch an action based on Redux. That’s why the dispatch function needs to be passed to this component as a property. In our scenario, this is appendCounter().

Creating an Action

Create a folder named ‘actions’ inside SRC containing index.js:

// index.js 
import * as actionType from './Actionform';
export const appendCounter = () => ({
  type: actionType.APPEND_COUNTER,
  payload: 1
});

Defining Actions with Actionform.js

Actionform.js can be found under the directory named actions.

// AppendCounter.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { appendCounter } from '../actions';
import { bindActionCreators } from 'redux';
class AppendCounter extends Component {
  constructor(props) {
        super(props);
      }
   render() {
     return (
           <div>
            <form>
              <div>
                <div>
                  <button 
                    onClick={(e) => {e.preventDefault();this.props.dispatch(appendCounter())}}>
                      Add
                   </button?
                </div>
              </div>
            </form>
            </div>
     )
   }
}
function mapDispatchWithProps(dispatch) {
  return { actions: bindActionCreators(appendCounter, dispatch) }
}
export default connect(mapDispatchWithProps)(AppendCounter);

The function mapDispatchWithProps is required to transfer the dispatch to the component as a property binding the actions with this component.

Creating the Reducer

Create a reducer directory within the SRC and make a file named counterReducer.js inside it:

// countReducer.js
import * as actionForm from '../actions/ActionForm;
const countReducer = (state = 0, action) => {
  let freshState;
  switch (action.type) {
    case actionForm.Append_COUNTER:
      return freshState = state + action.payload;
    case actionForm.DESTROY_COUNTER:
      return freshState = state - action.payload;
    default:
      return state
  }
}
export default countReducer;

Both conditions i.e. the increment and decrement have now been defined. You may notice that the state has not been modified directly since a new variable is defined which is then assigned a new state, and then is returned.

This function is pure, since it has not mutated any state of the store. It picks the previous state value and adds it with the new value, which is then assigned to this new variable and is returned as the application’s new state. This is the core principle of Redux.

Creating Index.Js

Make a file inside the reducer directory named index.js

// index.js
import { combineReducers } from 'redux';
import countReducer from './countReducer';
const countApp = combineReducers({
  countReducer
})
export default countApp

This is the only store that will be included in the main.js. The combineReducer() command then combines all the reducers in a store and sends back the state object like a global app.

Now we need bind the store and the reducer within the entire application:

// main.js
import React from 'react';
import { render } from 'react-DOM';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './components/App';
import reducer from './reducers';
const store = createStore(reducer);
render(
  <Provider store={store}>
    <App />
  </Providers>
  document.getElementById('root')
)

Creating DestroyCounter.js

Create a smart component file inside the container dir named as DestroyCounter.js:

// DestroyCounter.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { destroyCounter } from '../actions;
import { bindActionCreators } from 'redux';
class destroyCounter extends Component {
  constructor(props) {
    super(props);
  }
   render() {
     return (
           <div>
            <form>
              <div>
                <div>
                  <button 
                      onClick={(b) => {b.preventDefault();this.props.dispatch(destroyCounter())}}>
                      Remove
                  </buttons>
                </div>
              </div>
            </form>
            </div>
     )
   }
}
function mapDispatchWithProps(dispatch) {
  return { actions: bindActionCreators(destroyCounter, dispatch) }
}
export default connect(mapDispatchWithProps)(destroyCounter);

To add the new action for destroyCounter, we need edit the src/actions/index.js file:

// index.js
import * as actionType from './ActionForm';
export const appendCounter = () => ({
  type: actionType.APPEND_COUNTER,
  payload: 1
});
export const destroyCounter = () => ({
  type: actionType.DESTROY_COUNTER,
  payload: 1
});
And we have defined the Decrement reducer already.
// counterReducer.js
import * as actionType from '../actions/ActionForm';
const counterReducer = (state = 0, action) => {
  let freshState;
  switch (action.type) {
    case actionType.APPEND_COUNTER:
      return freshstate = state + action.payload;
    case actionType.DESTROY_COUNTER:
      return freshState = state - action.payload;
    default:
      return state
  }
}
export default counterReducer;

Adding Counter.js

Counter.js is a smart component which needs to be connected with the redux store to get and display the current state. The mapStateWithProps does the mapping of the state with the props of the latest component and then it displays the data as components property.

// index.js
import * as actionType from './ActionForm';
export const appendCounter = () => ({
  type: actionType.APPEND_COUNTER,
  payload: 1
});
export const destroyCounter = () => ({
  type: actionType.DESTROY_COUNTER,
  payload: 1
});
And we have defined the Decrement reducer already.
// counterReducer.js
import * as actionType from '../actions/ActionForm';
const counterReducer = (state = 0, action) => {
  let freshState;
  switch (action.type) {
    case actionType.APPEND_COUNTER:
    return freshstate = state + action.payload;
    case actionType.DESTROY_COUNTER:
    return freshState = state - action.payload;
    default:
    return state
  }
}
export default counterReducer;

Adding Components To App.Js

Now all we have to do is bring everything we’ve created to the application file:

// App.js
import React from 'react';
import Counter from '../containers/Counter';
import AddCounter from '../containers/AddCounter';
import DestroyCounter from '../containers/DestroyCounter';
const App = () => {
  return (
    <div>
      <Counter></Counter><br />
      <div>
        <div>
          <AddCounter></AddCounter>
        </div>
        <div>
          <RemoveCounter></RemoveCounter>
        </div>
      </div>
      </div>
  )
}
export default App;
npm start;

Finally, the http://localhost:9000 URL will display the full view of the app.

Conclusion

Hopefully, we’ve proved that Redux is definitely a great state management framework for React.js. The detailed process discussed above makes it obvious that for single-page real-life applications the React-Redux option is one of the best solutions. You can take the basics from this implementation and scale it to fit your project. Make state management with Redux your daily routine, as it will most likely come in handy.

Top comments (0)