Author avatar

Chris Parker

How to Integrate Redux Inside a React App

Chris Parker

  • May 5, 2020
  • 22 Min read
  • 1,866 Views
  • May 5, 2020
  • 22 Min read
  • 1,866 Views
Web Development
React

Introduction

This guide explains the best approach to using Redux in React and provides an example of how to add Redux on top of existing work.

Creating a New React.js-based Project and Adding Redux to it

Begin by creating a new React app.

1
2
3
create-react-app react-redux-tutorial
cd react-redux-tutorial
npm start
bash

Start by adding the required Redux packages as follows:

1
npm install --save redux react-redux
bash

Redux is a state management library that produces a global state for the entire app so that it can be accessed by all components. It is important that you have one global state for the entire app and not separate states for each component.

You will need two actions: startAction.js and stopAction.js. The next step is editing the src/actions/startAction.js:

1
2
3
4
export const startAction = {
  type: "rotate",
  payload: true
};
js

Here you are basically telling your reducer that your action type is rotate, as in the rotation of the React logo. Thus, the state of the rotations will be set to true when you want the logo to start rotating.

Next, edit the src/actions/stopAction.js:

1
2
3
4
export const stopAction = {
  type: "rotate",
  payload: false
};
js

The state of the rotations will be set to false when you want the logo to stop rotating.

Create the reducer rotateReducer.js and place the code below within it.

1
2
3
4
5
6
7
8
9
10
export default (state, action) => {
  switch (action.type) {
    case "rotate":
      return {
        rotating: action.payload
      };
    default:
      return state;
  }
};
js

Now the reducer is ready to receive both actions and will modify the same state within the app depending on the payload of the action.

The default case will maintain the current state should the action type not be rotate. The default case is especially important in cases where you may create a new action but forget to assign a case for it.

The final step is creating a store for the app. Because every app has only one store and one state, you do not need to create a separate new folder for the store. However, you can organize your files by creating a new folder for the store and keeping it there:

1
2
3
4
5
6
7
8
import { createStore } from "redux";
import rotateReducer from "reducers/rotateReducer";

function configureStore(state = { rotating: true }) {
  return createStore(rotateReducer,state);
}

export default configureStore;
js

Having prepared the store, actions, and reducer, the next step is adding a new class within the src/App.css file to halt the rotation of the animated logo.

Insert the following code within src/App.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.App-logo-paused {
  animation-play-state: paused;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
}

.App-logo-paused {
  animation-play-state: paused;
}

.App-header {
  background-color: #282c34;
  display: flex;
  flex-direction: column;  
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
css

Next, edit the src/App.js file for it to listen to the state of your store. Upon clicking the logo, it will call for either the start or stop actions.

Begin by connecting all components to the redux store so you can import connect from react-redux. After that, export your app component using the connect method. To edit the state of the redux store, import the actions you previously set up:

1
2
import { startAction } from "actions/startAction";
import { stopAction } from "actions/stopAction";
js

Next, retrieve the state from your store and inform the app that you require the start and stop actions to modify the state by using the connect function.

Next, append your retrieve:

1
2
3
4
5
6
7
8
const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
  startAction: () => dispatch(startAction),
  stopAction: () => dispatch(stopAction)
});
javascript

Insert them within the connect function:

1
export default connect(mapStateToProps, mapDispatchToProps)(App);
javascript

Now, you can access each of the store states, the startAction, and the stopAction through props from within your app component.

Modify the img tag:

1
2
3
4
5
6
7
8
9
10
11
12
<img 
  src={logo} 
  className={
    "App-logo" + 
    (this.props.rotating ? "":" App-logo-paused")
  } 
  alt="logo" 
  onClick={
    this.props.rotating ? 
      this.props.stopAction : this.props.startAction
  }
/>
jsx

Next, render your app through the provider utilizing the store instead of directly rendering the app component.

1
2
3
4
5
6
ReactDOM.render(
  <Provider store={configureStore()}>
    <App />
  </Provider>,
  document.getElementById('root')
);
js

Using Action Creators

Another option is to use action creators instead of actions. Action creators are simply functions that create actions.

Create a new rotateAction.js file and insert this piece of code within:

1
2
3
4
5
6
7
const rotateAction = (payload) => {
  return {
    type: "rotate",
    payload
  }
}
export default rotateAction;
jsx

Now, the onClick function must be modified:

1
onClick={() => this.props.rotateAction(!this.props.rotating)}
js

The final step is appending your new action creator to the mapDispatchToProps:

1
rotateAction: (payload) => dispatch(rotateAction(payload))
js

Now, your new src/App.js will be as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// import statements

class App extends Component {
  render() {
    console.log(this.props);
    return (
      <div className="App">
        <header className="App-header">
          <img
            src={logo}
            className={
              "App-logo" +
              (this.props.rotating ? "":" App-logo-paused")
            }
            alt="logo"
            onClick={
              () => this.props.rotateAction(!this.props.rotating)
            }
          />
        </header>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  rotateAction: (payload) => dispatch(rotateAction(payload))
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
jsx

A Real Life Example

Assume you have a menu on the page to alter the colors of the left menu. You can do that by employing component states and passing the said state from the parent component into the menus along with the functions responsible for modifying the state.

This is a simple example to illustrate using Redux instead of the component states.

Create the actions setBgAction.js and setColorAction.js:

  • For src/actions/setBgAction.js:
1
2
3
4
5
6
7
const setBgAction = (payload) => {
  return {
    type: "bgChange",
    payload
  }
}
export default setBgAction;
js
  • For src/actions/setColorAction.js:
1
2
3
4
5
6
7
const setColorAction = (payload) => {
  return {
    type: "colorChange",
    payload
  }
}
export default setColorAction;
js

Next, create the reducer, rootReducer.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default (state, action) => {
  switch (action.type) {
    case "bgChange":
      return {
        ...state,
        bgColor: action.payload
      };
    case "colorChange":
      return {
        ...state,
        activeColor: action.payload
      };
    default:
      return state;
  }
};
js

Contrary to the first example, you must maintain the old state and modify its values.

Next, you need the store:

1
2
3
4
5
6
7
import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";

function configureStore(state = { bgColor: "black", activeColor: "info" }) {
  return createStore(rootReducer,state);
}
export default configureStore;
js

Insert the following code within the src/index.js:

1
2
3
4
5
// new imports start
import { Provider } from "react-redux";

import configureStore from "store";
// new imports stop
js

Next, edit the render function. You'll end up with a index.js similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//all import statements here

const hist = createBrowserHistory();

ReactDOM.render(
  <Provider store={configureStore()}>
    <Router history={hist}>
      <Switch>
        {indexRoutes.map((prop, key) => {
          return <Route path={prop.path} key={key} component={prop.component} />;
        })}
      </Switch>
    </Router>
  </Provider>,
  document.getElementById("root")
);
js

Modify src/layouts/Dashboard/Dashboard.jsx to remove the state along with the functions that modify the state.

Delete the constructor and get rid of the state functions. Also, remove the sidebar bgColor and activeColor props. You will be left with the following code within the dashboard layout component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//all import statements here
var ps;

class Dashboard extends React.Component {
  componentDidUpdate(e) {
    if (e.history.action === "PUSH") {
      this.refs.mainPanel.scrollTop = 0;
      document.scrollingElement.scrollTop = 0;
    }
  }
  render() {
    return (
      <div className="wrapper">
        <Sidebar
          {...this.props}
          routes={dashboardRoutes}
        />
        <div className="main-panel" ref="mainPanel">
          <Header {...this.props} />
          <Switch>
            {dashboardRoutes.map((prop, key) => {
              if (prop.pro) {
                return null;
              }
              if (prop.redirect) {
                return <Redirect from={prop.path} to={prop.pathTo} key={key} />;
              }
              return (
                <Route path={prop.path} component={prop.component} key={key} />
              );
            })}
          </Switch>
          <Footer fluid />
        </div>
        <FixedPlugin />
      </div>
    );
  }
}

export default Dashboard;
jsx

In the next step, connect the Sidebar and FixedPlugin components to the store.

In the src/components/FixedPlugin/FixedPlugin.jsx file, insert the following:

1
2
import setBgAction from "actions/setBgAction";
import setColorAction from "actions/setColorAction";
jsx

The export will look as follows:

1
2
3
4
5
6
7
8
9
10
const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});

export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);
jsx

Be careful with the following steps. You need to carefully and thoroughly make the following modifications:

  • Replace every occurrence of handleBgClick with setBgAction.
  • Replace every occurrence of handleActiveClick with setColorAction.

Now, your new FixedPlugin component will be similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// import statements

class FixedPlugin extends Component {
  constructor(props) {
    super(props);
    this.state = {
      classes: "dropdown show"
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    if (this.state.classes === "dropdown") {
      this.setState({ classes: "dropdown show" });
    } else {
      this.setState({ classes: "dropdown" });
    }
  }
  render() {
    return (
      <div className="fixed-plugin">
        <div className={this.state.classes}>
          <div onClick={this.handleClick}>
            <i className="fa fa-cog fa-2x" />
          </div>
          <ul>
            <li>
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.bgColor === "black"
                      ? "badge filter badge-dark active"
                      : "badge filter badge-dark"
                  }
                  data-color="black"
                  onClick={() => {
                    this.props.setBgAction("black");
                  }}
                />
                <span
                  className={
                    this.props.bgColor === "white"
                      ? "badge filter badge-light active"
                      : "badge filter badge-light"
                  }
                  data-color="white"
                  onClick={() => {
                    this.props.setBgAction("white");
                  }}
                />
              </div>
            </li>
            <li>
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.activeColor === "primary"
                      ? "badge filter badge-primary active"
                      : "badge filter badge-primary"
                  }
                  data-color="primary"
                  onClick={() => {
                    this.props.setColorAction("primary");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "info"
                      ? "badge filter badge-info active"
                      : "badge filter badge-info"
                  }
                  data-color="info"
                  onClick={() => {
                    this.props.setColorAction("info");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "success"
                      ? "badge filter badge-success active"
                      : "badge filter badge-success"
                  }
                  data-color="success"
                  onClick={() => {
                    this.props.setColorAction("success");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "warning"
                      ? "badge filter badge-warning active"
                      : "badge filter badge-warning"
                  }
                  data-color="warning"
                  onClick={() => {
                    this.props.setColorAction("warning");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "danger"
                      ? "badge filter badge-danger active"
                      : "badge filter badge-danger"
                  }
                  data-color="danger"
                  onClick={() => {
                    this.props.setColorAction("danger");
                  }}
                />
              </div>
            </li>                    
          </ul>
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});

export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);
jsx

You can check your changes now.

Multiple Reducers

Just like it is possible to have several actions, it is also possible to have multiple reducers. However, there is one condition: you must combine them.

Start by creating two new reducers for your app: one of them will be dedicated for the setBgAction while the other one will handle the setColorAction. Next, write the new reducers' code:

  • In src/reducers/bgReducer.js:
1
2
3
4
5
6
7
8
9
10
11
export default (state = {}, action) => {
  switch (action.type) {
    case "bgChange":
      return {
        ...state,
        bgColor: action.payload
      };
    default:
      return state;
  }
};
js
  • In src/reducers/colorReducer.js:
1
2
3
4
5
6
7
8
9
10
11
export default (state = {} , action) => {
  switch (action.type) {
    case "colorChange":
      return {
        ...state,
        activeColor: action.payload
      };
    default:
      return state;
  }
};
js

Note that when you work with multiple reducers, you must never forget to add a default state in each of the reducers you plan to combine. In this guide an empty object, i.e. state = {}, will be used.

Use rootReducer to combine the reducers:

  • In src/reducers/rootReducer.js:
1
2
3
4
export default combineReducers({
  activeState: colorReducer,
  bgState: bgReducer
});
js

This indicates that you wish to refer to colorReducer and bgReducer by the activeState prop and the bgState prop of the app state, respectively. Having altered your reducers and combined them into one, now you must modify your store.js.

1
2
3
4
5
6
7
import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";

function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) {
  return createStore(rootReducer,state);
}
export default configureStore;
js

Your final code will be as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//import statements

class FixedPlugin extends Component {
  constructor(props) {
    super(props);
    this.state = {
      classes: "dropdown show"
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    if (this.state.classes === "dropdown") {
      this.setState({ classes: "dropdown show" });
    } else {
      this.setState({ classes: "dropdown" });
    }
  }
  render() {
    return (
      <div className="fixed-plugin">
        <div className={this.state.classes}>
          <div onClick={this.handleClick}>
            <i className="fa fa-cog fa-2x" />
          </div>
          <ul className="dropdown-menu show">
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.bgState.bgColor === "black"
                      ? "badge filter badge-dark active"
                      : "badge filter badge-dark"
                  }
                  data-color="black"
                  onClick={() => {
                    this.props.setBgAction("black");
                  }}
                />
                <span
                  className={
                    this.props.bgState.bgColor === "white"
                      ? "badge filter badge-light active"
                      : "badge filter badge-light"
                  }
                  data-color="white"
                  onClick={() => {
                    this.props.setBgAction("white");
                  }}
                />
              </div>
            </li>
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.activeState.activeColor === "primary"
                      ? "badge filter badge-primary active"
                      : "badge filter badge-primary"
                  }
                  data-color="primary"
                  onClick={() => {
                    this.props.setColorAction("primary");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "info"
                      ? "badge filter badge-info active"
                      : "badge filter badge-info"
                  }
                  data-color="info"
                  onClick={() => {
                    this.props.setColorAction("info");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "success"
                      ? "badge filter badge-success active"
                      : "badge filter badge-success"
                  }
                  data-color="success"
                  onClick={() => {
                    this.props.setColorAction("success");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "warning"
                      ? "badge filter badge-warning active"
                      : "badge filter badge-warning"
                  }
                  data-color="warning"
                  onClick={() => {
                    this.props.setColorAction("warning");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "danger"
                      ? "badge filter badge-danger active"
                      : "badge filter badge-danger"
                  }
                  data-color="danger"
                  onClick={() => {
                    this.props.setColorAction("danger");
                  }}
                />
              </div>
            </li>                   
          </ul>
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  ...state
});

const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});

export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);
jsx

Conclusion

Now, you have a wonderful working project that implements Redux on top of React. Start it again with npm start and enjoy!

12