Author avatar

Chris Parker

How to Integrate Redux Inside a React App

Chris Parker

  • May 5, 2020
  • 22 Min read
  • 7,846 Views
  • May 5, 2020
  • 22 Min read
  • 7,846 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.

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

Start by adding the required Redux packages as follows:

1npm 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:

1export const startAction = {
2  type: "rotate",
3  payload: true
4};
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:

1export const stopAction = {
2  type: "rotate",
3  payload: false
4};
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.

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

1import { createStore } from "redux";
2import rotateReducer from "reducers/rotateReducer";
3
4function configureStore(state = { rotating: true }) {
5  return createStore(rotateReducer,state);
6}
7
8export 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.App-logo-paused {
2  animation-play-state: paused;
3}
4
5.App-logo {
6  animation: App-logo-spin infinite 20s linear;
7  height: 40vmin;
8}
9
10.App-logo-paused {
11  animation-play-state: paused;
12}
13
14.App-header {
15  background-color: #282c34;
16  display: flex;
17  flex-direction: column;  
18}
19
20@keyframes App-logo-spin {
21  from {
22    transform: rotate(0deg);
23  }
24  to {
25    transform: rotate(360deg);
26  }
27}
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:

1import { startAction } from "actions/startAction";
2import { 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:

1const mapStateToProps = state => ({
2  ...state
3});
4
5const mapDispatchToProps = dispatch => ({
6  startAction: () => dispatch(startAction),
7  stopAction: () => dispatch(stopAction)
8});
javascript

Insert them within the connect function:

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

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

1ReactDOM.render(
2  <Provider store={configureStore()}>
3    <App />
4  </Provider>,
5  document.getElementById('root')
6);
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:

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

Now, the onClick function must be modified:

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

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

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

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

1// import statements
2
3class App extends Component {
4  render() {
5    console.log(this.props);
6    return (
7      <div className="App">
8        <header className="App-header">
9          <img
10            src={logo}
11            className={
12              "App-logo" +
13              (this.props.rotating ? "":" App-logo-paused")
14            }
15            alt="logo"
16            onClick={
17              () => this.props.rotateAction(!this.props.rotating)
18            }
19          />
20        </header>
21      </div>
22    );
23  }
24}
25
26const mapStateToProps = state => ({
27  ...state
28});
29const mapDispatchToProps = dispatch => ({
30  rotateAction: (payload) => dispatch(rotateAction(payload))
31});
32
33export 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:
1const setBgAction = (payload) => {
2  return {
3    type: "bgChange",
4    payload
5  }
6}
7export default setBgAction;
js
  • For src/actions/setColorAction.js:
1const setColorAction = (payload) => {
2  return {
3    type: "colorChange",
4    payload
5  }
6}
7export default setColorAction;
js

Next, create the reducer, rootReducer.js:

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

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

Next, you need the store:

1import { createStore } from "redux";
2import rootReducer from "reducers/rootReducer";
3
4function configureStore(state = { bgColor: "black", activeColor: "info" }) {
5  return createStore(rootReducer,state);
6}
7export default configureStore;
js

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

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

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

1//all import statements here
2
3const hist = createBrowserHistory();
4
5ReactDOM.render(
6  <Provider store={configureStore()}>
7    <Router history={hist}>
8      <Switch>
9        {indexRoutes.map((prop, key) => {
10          return <Route path={prop.path} key={key} component={prop.component} />;
11        })}
12      </Switch>
13    </Router>
14  </Provider>,
15  document.getElementById("root")
16);
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//all import statements here
2var ps;
3
4class Dashboard extends React.Component {
5  componentDidUpdate(e) {
6    if (e.history.action === "PUSH") {
7      this.refs.mainPanel.scrollTop = 0;
8      document.scrollingElement.scrollTop = 0;
9    }
10  }
11  render() {
12    return (
13      <div className="wrapper">
14        <Sidebar
15          {...this.props}
16          routes={dashboardRoutes}
17        />
18        <div className="main-panel" ref="mainPanel">
19          <Header {...this.props} />
20          <Switch>
21            {dashboardRoutes.map((prop, key) => {
22              if (prop.pro) {
23                return null;
24              }
25              if (prop.redirect) {
26                return <Redirect from={prop.path} to={prop.pathTo} key={key} />;
27              }
28              return (
29                <Route path={prop.path} component={prop.component} key={key} />
30              );
31            })}
32          </Switch>
33          <Footer fluid />
34        </div>
35        <FixedPlugin />
36      </div>
37    );
38  }
39}
40
41export 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:

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

The export will look as follows:

1const mapStateToProps = state => ({
2  ...state
3});
4
5const mapDispatchToProps = dispatch => ({
6  setBgAction: (payload) => dispatch(setBgAction(payload)),
7  setColorAction: (payload) => dispatch(setColorAction(payload))
8});
9
10export 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// import statements
2
3class FixedPlugin extends Component {
4  constructor(props) {
5    super(props);
6    this.state = {
7      classes: "dropdown show"
8    };
9    this.handleClick = this.handleClick.bind(this);
10  }
11  handleClick() {
12    if (this.state.classes === "dropdown") {
13      this.setState({ classes: "dropdown show" });
14    } else {
15      this.setState({ classes: "dropdown" });
16    }
17  }
18  render() {
19    return (
20      <div className="fixed-plugin">
21        <div className={this.state.classes}>
22          <div onClick={this.handleClick}>
23            <i className="fa fa-cog fa-2x" />
24          </div>
25          <ul>
26            <li>
27              <div className="badge-colors text-center">
28                <span
29                  className={
30                    this.props.bgColor === "black"
31                      ? "badge filter badge-dark active"
32                      : "badge filter badge-dark"
33                  }
34                  data-color="black"
35                  onClick={() => {
36                    this.props.setBgAction("black");
37                  }}
38                />
39                <span
40                  className={
41                    this.props.bgColor === "white"
42                      ? "badge filter badge-light active"
43                      : "badge filter badge-light"
44                  }
45                  data-color="white"
46                  onClick={() => {
47                    this.props.setBgAction("white");
48                  }}
49                />
50              </div>
51            </li>
52            <li>
53              <div className="badge-colors text-center">
54                <span
55                  className={
56                    this.props.activeColor === "primary"
57                      ? "badge filter badge-primary active"
58                      : "badge filter badge-primary"
59                  }
60                  data-color="primary"
61                  onClick={() => {
62                    this.props.setColorAction("primary");
63                  }}
64                />
65                <span
66                  className={
67                    this.props.activeColor === "info"
68                      ? "badge filter badge-info active"
69                      : "badge filter badge-info"
70                  }
71                  data-color="info"
72                  onClick={() => {
73                    this.props.setColorAction("info");
74                  }}
75                />
76                <span
77                  className={
78                    this.props.activeColor === "success"
79                      ? "badge filter badge-success active"
80                      : "badge filter badge-success"
81                  }
82                  data-color="success"
83                  onClick={() => {
84                    this.props.setColorAction("success");
85                  }}
86                />
87                <span
88                  className={
89                    this.props.activeColor === "warning"
90                      ? "badge filter badge-warning active"
91                      : "badge filter badge-warning"
92                  }
93                  data-color="warning"
94                  onClick={() => {
95                    this.props.setColorAction("warning");
96                  }}
97                />
98                <span
99                  className={
100                    this.props.activeColor === "danger"
101                      ? "badge filter badge-danger active"
102                      : "badge filter badge-danger"
103                  }
104                  data-color="danger"
105                  onClick={() => {
106                    this.props.setColorAction("danger");
107                  }}
108                />
109              </div>
110            </li>                    
111          </ul>
112        </div>
113      </div>
114    );
115  }
116}
117
118const mapStateToProps = state => ({
119  ...state
120});
121
122const mapDispatchToProps = dispatch => ({
123  setBgAction: (payload) => dispatch(setBgAction(payload)),
124  setColorAction: (payload) => dispatch(setColorAction(payload))
125});
126
127export 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:
1export default (state = {}, action) => {
2  switch (action.type) {
3    case "bgChange":
4      return {
5        ...state,
6        bgColor: action.payload
7      };
8    default:
9      return state;
10  }
11};
js
  • In src/reducers/colorReducer.js:
1export default (state = {} , action) => {
2  switch (action.type) {
3    case "colorChange":
4      return {
5        ...state,
6        activeColor: action.payload
7      };
8    default:
9      return state;
10  }
11};
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:
1export default combineReducers({
2  activeState: colorReducer,
3  bgState: bgReducer
4});
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.

1import { createStore } from "redux";
2import rootReducer from "reducers/rootReducer";
3
4function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) {
5  return createStore(rootReducer,state);
6}
7export default configureStore;
js

Your final code will be as follows:

1//import statements
2
3class FixedPlugin extends Component {
4  constructor(props) {
5    super(props);
6    this.state = {
7      classes: "dropdown show"
8    };
9    this.handleClick = this.handleClick.bind(this);
10  }
11  handleClick() {
12    if (this.state.classes === "dropdown") {
13      this.setState({ classes: "dropdown show" });
14    } else {
15      this.setState({ classes: "dropdown" });
16    }
17  }
18  render() {
19    return (
20      <div className="fixed-plugin">
21        <div className={this.state.classes}>
22          <div onClick={this.handleClick}>
23            <i className="fa fa-cog fa-2x" />
24          </div>
25          <ul className="dropdown-menu show">
26            <li className="adjustments-line">
27              <div className="badge-colors text-center">
28                <span
29                  className={
30                    this.props.bgState.bgColor === "black"
31                      ? "badge filter badge-dark active"
32                      : "badge filter badge-dark"
33                  }
34                  data-color="black"
35                  onClick={() => {
36                    this.props.setBgAction("black");
37                  }}
38                />
39                <span
40                  className={
41                    this.props.bgState.bgColor === "white"
42                      ? "badge filter badge-light active"
43                      : "badge filter badge-light"
44                  }
45                  data-color="white"
46                  onClick={() => {
47                    this.props.setBgAction("white");
48                  }}
49                />
50              </div>
51            </li>
52            <li className="adjustments-line">
53              <div className="badge-colors text-center">
54                <span
55                  className={
56                    this.props.activeState.activeColor === "primary"
57                      ? "badge filter badge-primary active"
58                      : "badge filter badge-primary"
59                  }
60                  data-color="primary"
61                  onClick={() => {
62                    this.props.setColorAction("primary");
63                  }}
64                />
65                <span
66                  className={
67                    this.props.activeState.activeColor === "info"
68                      ? "badge filter badge-info active"
69                      : "badge filter badge-info"
70                  }
71                  data-color="info"
72                  onClick={() => {
73                    this.props.setColorAction("info");
74                  }}
75                />
76                <span
77                  className={
78                    this.props.activeState.activeColor === "success"
79                      ? "badge filter badge-success active"
80                      : "badge filter badge-success"
81                  }
82                  data-color="success"
83                  onClick={() => {
84                    this.props.setColorAction("success");
85                  }}
86                />
87                <span
88                  className={
89                    this.props.activeState.activeColor === "warning"
90                      ? "badge filter badge-warning active"
91                      : "badge filter badge-warning"
92                  }
93                  data-color="warning"
94                  onClick={() => {
95                    this.props.setColorAction("warning");
96                  }}
97                />
98                <span
99                  className={
100                    this.props.activeState.activeColor === "danger"
101                      ? "badge filter badge-danger active"
102                      : "badge filter badge-danger"
103                  }
104                  data-color="danger"
105                  onClick={() => {
106                    this.props.setColorAction("danger");
107                  }}
108                />
109              </div>
110            </li>                   
111          </ul>
112        </div>
113      </div>
114    );
115  }
116}
117
118const mapStateToProps = state => ({
119  ...state
120});
121
122const mapDispatchToProps = dispatch => ({
123  setBgAction: (payload) => dispatch(setBgAction(payload)),
124  setColorAction: (payload) => dispatch(setColorAction(payload))
125});
126
127export 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!