15
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.
1 2 3
create-react-app react-redux-tutorial cd react-redux-tutorial npm start
Start by adding the required Redux packages as follows:
1
npm 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
:
1 2 3 4
export const startAction = { type: "rotate", payload: true };
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 };
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; } };
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;
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); } }
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";
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) });
Insert them within the connect function:
1
export 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 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 } />
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') );
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;
Now, the onClick
function must be modified:
1
onClick={() => this.props.rotateAction(!this.props.rotating)}
The final step is appending your new action creator to the mapDispatchToProps
:
1
rotateAction: (payload) => dispatch(rotateAction(payload))
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);
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
:1 2 3 4 5 6 7
const setBgAction = (payload) => { return { type: "bgChange", payload } } export default setBgAction;
src/actions/setColorAction.js
:1 2 3 4 5 6 7
const setColorAction = (payload) => { return { type: "colorChange", payload } } export default setColorAction;
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; } };
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;
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
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") );
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;
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";
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);
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 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);
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
: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; } };
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; } };
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
:1 2 3 4
export default combineReducers({ activeState: colorReducer, bgState: bgReducer });
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;
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);
Now, you have a wonderful working project that implements Redux on top of React. Start it again with npm start
and enjoy!
15