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.
Begin by creating a new React app.
1create-react-app react-redux-tutorial
2cd react-redux-tutorial
3npm start
Start by adding the required Redux packages as follows:
1npm install --save redux react-redux
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};
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};
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};
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;
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}
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";
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});
Insert them within the connect function:
1export default connect(mapStateToProps, mapDispatchToProps)(App);
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/>
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);
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;
Now, the onClick
function must be modified:
1onClick={() => this.props.rotateAction(!this.props.rotating)}
The final step is appending your new action creator to the mapDispatchToProps
:
1rotateAction: (payload) => dispatch(rotateAction(payload))
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);
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
:
src/actions/setBgAction.js
:1const setBgAction = (payload) => {
2 return {
3 type: "bgChange",
4 payload
5 }
6}
7export default setBgAction;
src/actions/setColorAction.js
:1const setColorAction = (payload) => {
2 return {
3 type: "colorChange",
4 payload
5 }
6}
7export default setColorAction;
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};
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;
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
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);
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;
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";
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);
Be careful with the following steps. You need to carefully and thoroughly make the following modifications:
handleBgClick
with setBgAction
.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);
You can check your changes now.
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:
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};
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};
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:
src/reducers/rootReducer.js
:1export default combineReducers({
2 activeState: colorReducer,
3 bgState: bgReducer
4});
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;
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);
Now, you have a wonderful working project that implements Redux on top of React. Start it again with npm start
and enjoy!