Cómo usar Redux en ReactJS con ejemplos de la vida real

Desde que comencé a trabajar con ReactJS, en Creative-Tim, solo lo he usado para crear aplicaciones de reacción simples, o plantillas si lo desea. He usado ReactJS solo con create-react-app y nunca he intentado integrarlo con algo más.

Muchos de nuestros usuarios me han preguntado a mí oa mi equipo si las plantillas creadas por mí tenían Redux. O si fueron creados de tal manera que pudieran usarse con Redux. Y mi respuesta siempre fue algo así como: “Todavía no he trabajado con Redux y no sé qué respuesta debo darte”.

Así que aquí estoy ahora, escribiendo un artículo sobre Redux y cómo debería usarse en React. Más adelante, en este artículo, voy a agregar Redux a uno de los proyectos en los que he trabajado durante los últimos años.

Es bueno saberlo antes de seguir adelante y luchar con estas dos bibliotecas:

  • Voy a usar [email protected] (instalado globalmente)
  • Estoy usando [email protected]
  • Mi versión de Node.js al momento de escribir esta publicación era 10.13.0 (LTS)
  • Si desea utilizar Webpack en su lugar, puede leer mi artículo de Webpack y combinar lo que le muestro allí con lo que le voy a mostrar aquí.

Crear un nuevo proyecto basado en ReactJS y agregarle Redux

Lo primero es lo primero, creemos una nueva aplicación de reacción, acceda a ella e iníciela.

create-react-app react-redux-tutorial cd react-redux-tutorial npm start

Como podemos ver, create-react-app nos brinda una plantilla muy básica con un párrafo, un ancla al sitio web de React y el ícono oficial de ReactJS girando.

No les he dicho para qué vamos a usar Redux, o qué estamos haciendo aquí. Y esto se debe a que necesitaba la imagen gif anterior.

Para que este artículo tutorial sea liviano y fácil de entender, no vamos a construir algo muy complejo. Vamos a usar Redux para hacer que la imagen de React anterior se detenga o comience a rotar.

Dicho esto, sigamos adelante y agreguemos los siguientes paquetes de Redux :

npm install --save redux react-redux

redux v4.0.1

  • Lo que hace Redux en un sentido muy general, es que crea un estado global para toda la aplicación, al que puede acceder cualquiera de sus componentes
  • Es una biblioteca de gestión estatal.
  • Solo tiene un estado para toda su aplicación y no estados para cada uno de sus componentes

react-redux v5.1.1

  • Esto se usa para que podamos acceder a los datos de Redux y modificarlos enviando acciones a Redux, en realidad no a Redux, pero llegaremos allí.
  • El estado de los documentos oficiales: permite a sus componentes React leer datos de una tienda Redux y enviar acciones a la tienda para actualizar los datos

NOTA : Si tiene problemas con el comando anterior, intente instalar los paquetes por separado

Cuando trabaje con Redux, necesitará tres cosas principales:

  • acciones: estos son objetos que deben tener dos propiedades, una que describe el tipo de acción y otra que describe qué se debe cambiar en el estado de la aplicación.
  • reductores: son funciones que implementan el comportamiento de las acciones. Cambian el estado de la aplicación, según la descripción de la acción y la descripción del cambio de estado.
  • tienda: reúne las acciones y los reductores, manteniendo y cambiando el estado de toda la aplicación; solo hay una tienda.

Como dije anteriormente, vamos a detenernos y comenzar a girar el logo de React. Esto significa que vamos a necesitar dos acciones de la siguiente manera:

1 - Comandos de Linux / Mac

mkdir src/actions touch src/actions/startAction.js touch src/actions/stopAction.js

2 - comandos de Windows

mkdir src\actions echo "" > src\actions\startAction.js echo "" > src\actions\stopAction.js

Ahora editemos src / actions / startAction.js de la siguiente manera:

export const startAction = { type: "rotate", payload: true };

Entonces, vamos a decirle a nuestro reductor que el tipo de acción se trata de la rotación ( rotar ) del logo de React. Y el estado de rotación del logotipo de React debe cambiarse a verdadero : queremos que el logotipo comience a girar.

Ahora editemos src / actions / stopAction.js de la siguiente manera:

export const stopAction = { type: "rotate", payload: false };

So, we are going to say to our reducer that the type of the action is about the rotation (rotate) of the React logo. And the state for the rotate of the React logo should be changed to false — we want the logo to stop rotating.

Let’s also create the reducer for our app:

1 — Linux / Mac commands

mkdir src/reducers touch src/reducers/rotateReducer.js

2 — Windows commands

mkdir src\reducers echo "" > src\reducers\rotateReducer.js

And, add the following code inside of it:

export default (state, action) => { switch (action.type) { case "rotate": return { rotating: action.payload }; default: return state; } };

So, the reducer will receive both of our actions, both of which are of type rotate, and they both change the same state in the app — which is state.rotating. Based on the payload of these actions, state.rotating will change into true or false.

I’ve added a default case, which will keep the state unaltered if the action type is not rotate. The default value is there in case we create an action and we forget to add a case for that action. This way we do not delete the whole app state — we simply do nothing, and keep what we had.

The last thing that we need to do is to create our store for the whole app. Since there is only one store / one state for the whole app, we are not going to create a new folder for the store. If you want, you can create a new folder for the store and add it there, but it’s not like with the actions, for example, where you can have multiple actions and it looks better to keep them inside a folder.

So this being said we are going to run this command:

1 — Linux / Mac command

touch src/store.js

2 — Windows command

echo "" > src\store.js

And also add the following code inside it:

import { createStore } from "redux"; import rotateReducer from "reducers/rotateReducer"; function configureStore(state = { rotating: true }) { return createStore(rotateReducer,state); } export default configureStore;

So, we create a function named configureStore in which we send a default state, and we create our store using the created reducer and the default state.

I’m not sure if you’ve seen my imports, they use absolute paths, so you might have some errors due to this. The fix for this is one of the two:

Either

1 — Add a .env file into your app like so:

echo "NODE_PATH=./src" > .env

Or

2 — Install cross-env globally and change the start script from the package.json file like so:

npm install -g cross-env

And inside package.json

"start": "NODE_PATH=./src react-scripts start",

Now that we have set up our store, our actions and our reducer we need to add a new class inside the src/App.css file. This class will pause the rotating animation of the logo.

So we are going to write the following inside src/App.css:

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

So your App.css file should look something like this:

.App { text-align: center; } .App-logo { animation: App-logo-spin infinite 20s linear; height: 40vmin; } /* new class here */ .App-logo-paused { animation-play-state: paused; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

Now, we only need to modify our src/App.js file so that it listens to our store state. And when clicking on the logo, it calls one of the start or stop actions.

First things first, we need to connect our component to our redux store so we import connect from react-redux.

import { connect } from "react-redux";

After this, we’ll export our App component through the connect method like this:

export default connect()(App);

To change the redux store state we’ll need the actions that we’ve done earlier so let’s import them as well:

import { startAction } from "actions/startAction"; import { stopAction } from "actions/stopAction";

Now we need to retrieve the state from our store and to say that we want the start and stop actions to be used for changing the state.

This will be done using the connect function, which accepts two parameters:

  • mapStateToProps: this is used to retrieve the store state
  • mapDispatchToProps: this is used to retrieve the actions and dispatch them to the store

You can read more about them here: react-redux connect function arguments.

So let’s write inside our App.js (at the end of the file if you may):

const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ startAction: () => dispatch(startAction), stopAction: () => dispatch(stopAction) });

After this, let’s add them inside our connect function like so:

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

And right now, inside our App component, we can access the store state, the startAction and stopAction through props.

Let’s change the img tag to:

So, what we are saying here is, if the store state of rotating (this.props.rotating) is true, then we want just the App-logoclassName to be set to our img. If that is false, then we also want the App-logo-paused class to be set in the className. This way we pause the animation.

Also, if this.props.rotating is true, then we want to send to our store for the onClick function and change it back to false, and vice-versa.

We are almost done, but we’ve forgot something.

We haven’t yet told our react app that we have a global state, or if you will, that we use redux state management.

For this, we go inside src/index.js, we import a Provider from react-redux, and the newly created store like so:

import { Provider } from "react-redux"; import configureStore from "store";
  • Provider: makes the Redux store available to any nested components that have been wrapped in the connect function

After this, instead of rendering our App component directly, we render it through our Provider using the store that we’ve created like so:

ReactDOM.render(   , document.getElementById('root') );

Here we could have used the configureStore function with some other state, for example configureStore({ rotating: false }).

So, your index.js should look like this:

import React from 'react'; import ReactDOM from 'react-dom'; // new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; // changed the render ReactDOM.render(   , document.getElementById('root') ); // changed the render serviceWorker.unregister();

Let’s go ahead and see if our redux app works:

Using action creators

Optionally, instead of actions, we can use action creators, which are functions that create actions.

This way, we can combine our two actions in just one function and reduce a bit our code.

So, let’s go ahead and create a new file:

1 — Linux / Mac command

touch src/actions/rotateAction.js

2 — Windows command

echo "" > src\actions\rotateAction.js

And add this code:

const rotateAction = (payload) => { return { type: "rotate", payload } } export default rotateAction;

We are going to send an action of type rotate, with a payload that we are going to get in the App component.

Inside the src/App.js component, we need to import our new action creator:

import rotateAction from "actions/rotateAction";

Add the new function to the mapDispatchToProps like so:

rotateAction: will receive a (payload) and will dispatch the rotateAction with the payload

Change the onClick function to:

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

And finally, add our new action creator to the mapDispatchToProps like this:

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

We can also delete the old imports for the old actions, and delete them from the mapDispatchToProps as well.

This is how you new src/App.js should look like:

import React, { Component } from 'react'; // new lines from here import { connect } from "react-redux"; import rotateAction from "actions/rotateAction"; //// new lines to here import logo from './logo.svg'; import './App.css'; class App extends Component { render() { console.log(this.props); return (  this.props.rotateAction(!this.props.rotating) } />

Edit src/App.js and save to reload.

Learn React ); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ rotateAction: (payload) => dispatch(rotateAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(App);

A real-life example with Paper Dashboard React

As you will see in the above gif image, I am using the right menu to change the colors of the menu on the left. This is achieved by using component states, and by passing that state from a parent component to the two menus and some functions to change that state.

I thought it would be a nice example, to take this product and replace the component states with Redux.

You can get it in these 3 ways:

  1. Download from creative-tim.com
  2. Download from Github
  3. Clone from Github:
git clone //github.com/creativetimofficial/paper-dashboard-react.git

Now that we have this product, let’s cd into it and install again redux and react-redux:

npm install --save redux react-redux

After this, we need to create the actions. Since in the right menu we have 2 colors that set the background of the left menu, and 5 colors that change the color of the links, we need 7 actions, or 2 actions creators — and we are going with this second option since it is a bit less code to write:

1 — Linux / Mac commands

mkdir src/actions touch src/actions/setBgAction.js touch src/actions/setColorAction.js

2 — Windows commands

mkdir src\actions echo "" > src\actions\setBgAction.js echo "" > src\actions\setColorAction.js

After this, let’s create the actions code as follows:

src/actions/setBgAction.js

const setBgAction = (payload) => { return { type: "bgChange", payload } } export default setBgAction;

src/actions/setColorAction.js

const setColorAction = (payload) => { return { type: "colorChange", payload } } export default setColorAction;

Now, as in the first part, we need the reducer:

1 — Linux / Mac commands

mkdir src/reducers touch src/reducers/rootReducer.js

2 — Windows commands

mkdir src\reducers echo "" > src\reducers\rootReducer.js

And the code for the reducer:

export default (state, action) => { switch (action.type) { case "bgChange": return { ...state, bgColor: action.payload }; case "colorChange": return { ...state, activeColor: action.payload }; default: return state; } };

As you can see here, unlike our first example, we want to keep our old state and update its contents.

We also need the store:

1 — Linux / Mac command

touch src/store.js

2 — Windows command

echo "" > src\store.js

The code for it:

import { createStore } from "redux"; import rootReducer from "reducers/rootReducer"; function configureStore(state = { bgColor: "black", activeColor: "info" }) { return createStore(rootReducer,state); } export default configureStore;

Inside the src/index.js we need:

// new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop

And also, change the render function:

ReactDOM.render(    {indexRoutes.map((prop, key) => { return ; })}   , document.getElementById("root") );

So the index.js file should look like this:

import React from "react"; import ReactDOM from "react-dom"; import { createBrowserHistory } from "history"; import { Router, Route, Switch } from "react-router-dom"; // new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop import "bootstrap/dist/css/bootstrap.css"; import "assets/scss/paper-dashboard.scss"; import "assets/demo/demo.css"; import indexRoutes from "routes/index.jsx"; const hist = createBrowserHistory(); ReactDOM.render(    {indexRoutes.map((prop, key) => { return ; })}   , document.getElementById("root") );

Now we need to make some changes inside src/layouts/Dashboard/Dashboard.jsx. We need to delete the state and the functions that change the state. So go ahead and delete these bits of code:

The constructor (between lines 16 and 22):

constructor(props){ super(props); this.state = { backgroundColor: "black", activeColor: "info", } }

The state functions (between lines 41 and 46):

handleActiveClick = (color) => { this.setState({ activeColor: color }); } handleBgClick = (color) => { this.setState({ backgroundColor: color }); }

The sidebar bgColor and activeColor props (lines 53 and 54):

bgColor={this.state.backgroundColor} activeColor={this.state.activeColor}

All of the FixedPlugin props (between lines 59–62):

bgColor={this.state.backgroundColor} activeColor={this.state.activeColor} handleActiveClick={this.handleActiveClick} handleBgClick={this.handleBgClick}

So, we remain with this code inside the Dashboard layout component:

import React from "react"; // javascript plugin used to create scrollbars on windows import PerfectScrollbar from "perfect-scrollbar"; import { Route, Switch, Redirect } from "react-router-dom"; import Header from "components/Header/Header.jsx"; import Footer from "components/Footer/Footer.jsx"; import Sidebar from "components/Sidebar/Sidebar.jsx"; import FixedPlugin from "components/FixedPlugin/FixedPlugin.jsx"; import dashboardRoutes from "routes/dashboard.jsx"; var ps; class Dashboard extends React.Component { componentDidMount() { if (navigator.platform.indexOf("Win") > -1) { ps = new PerfectScrollbar(this.refs.mainPanel); document.body.classList.toggle("perfect-scrollbar-on"); } } componentWillUnmount() { if (navigator.platform.indexOf("Win") > -1) { ps.destroy(); document.body.classList.toggle("perfect-scrollbar-on"); } } componentDidUpdate(e) { if (e.history.action === "PUSH") { this.refs.mainPanel.scrollTop = 0; document.scrollingElement.scrollTop = 0; } } render() { return ( {dashboardRoutes.map((prop, key) => { if (prop.pro) { return null; } if (prop.redirect) { return ; } return (  ); })} ); } } export default Dashboard;

We need to connect the Sidebar and FixedPlugin components to to the store.

For src/components/Sidebar/Sidebar.jsx:

import { connect } from "react-redux";

And change the export to:

const mapStateToProps = state => ({ ...state }); export default connect(mapStateToProps)(Sidebar);

For the src/components/FixedPlugin/FixedPlugin.jsx:

import { connect } from "react-redux"; import setBgAction from "actions/setBgAction"; import setColorAction from "actions/setColorAction";

And the export should now be:

const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

We are going to have these next changes:

  • anywhere you find the word handleBgClick, you’ll need to change it to setBgAction
  • anywhere you find the word handleActiveClick, you’ll need to change it to setColorAction

So, the FixedPlugin component should now look like this:

import React, { Component } from "react"; import { connect } from "react-redux"; import setBgAction from "actions/setBgAction"; import setColorAction from "actions/setColorAction"; import Button from "components/CustomButton/CustomButton.jsx"; 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 ( 
    
  • SIDEBAR BACKGROUND
  • { this.props.setBgAction("black"); }} /> { this.props.setBgAction("white"); }} />
  • SIDEBAR ACTIVE COLOR
  • { this.props.setColorAction("primary"); }} /> { this.props.setColorAction("info"); }} /> { this.props.setColorAction("success"); }} /> { this.props.setColorAction("warning"); }} /> { this.props.setColorAction("danger"); }} />
  • Download now
  • Documentation
  • Want more components?
  • Get pro version
); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

And we are done, you can start the project and see how everything works fine:

Multiple reducers

As you can have multiple actions, you can have multiple reducers. The only thing is that you need to combine them — we’ll see this a bit further down.

Let’s go ahead and create two new reducers for our app, one for the setBgAction and one for the setColorAction:

1 — Linux / Mac commands

touch src/reducers/bgReducer.js touch src/reducers/colorReducer.js

2 — Windows commands

echo "" > src\reducers\bgReducer.js echo "" > src\reducers\colorReducer.js

After this, let’s create the reducers’ code as follows:

src/reducers/bgReducer.js

export default (state = {}, action) => { switch (action.type) { case "bgChange": return { ...state, bgColor: action.payload }; default: return state; } };

src/reducers/colorReducer.js

export default (state = {} , action) => { switch (action.type) { case "colorChange": return { ...state, activeColor: action.payload }; default: return state; } };

When working with combined reducers, you need to add a default state in each of your reducers that are going to be combined. In my case, I’ve chosen an empty object i.e. state = {};

And now, our rootReducer will combine these two as follows:

src/reducers/rootReducer.js

import { combineReducers } from 'redux'; import bgReducer from 'reducers/bgReducer'; import colorReducer from 'reducers/colorReducer'; export default combineReducers({ activeState: colorReducer, bgState: bgReducer });

So, we say that we want the colorReducer to be referred by the activeState prop of the app state, and the bgReducer to be referred by the bgState prop of the app state.

This means that our state will no longer look like this:

state = { activeColor: "color1", bgColor: "color2" }

It will now look like this:

state = { activeState: { activeColor: "color1" }, bgState: { bgColor: "color2" } }

Since we’ve changed our reducers, now we’ve now combined them together into just one, we need to change our store.js as well:

src/store.js

import { createStore } from "redux"; import rootReducer from "reducers/rootReducer"; // we need to pass the initial state with the new look function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) { return createStore(rootReducer,state); } export default configureStore;

Since we’ve changed the way the state looks, we now need to change the props inside the Sidebar and FixedPlugin components to the new state object:

src/components/Sidebar/Sidebar.jsx:

Change line 36 from

to

src/components/FixedPlugin/FixedPlugin.jsx:

We need to change all the this.props.bgColor to this.props.bgState.bgColor . And all the this.props.activeColor to this.props.activeState.activeColor .

So the new code should look like this:

import React, { Component } from "react"; import Button from "components/CustomButton/CustomButton.jsx"; import { connect } from "react-redux"; import setBgAction from "actions/setBgAction"; import setColorAction from "actions/setColorAction"; 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 ( 
    
  • SIDEBAR BACKGROUND
  • { this.props.setBgAction("black"); }} /> { this.props.setBgAction("white"); }} />
  • SIDEBAR ACTIVE COLOR
  • { this.props.setColorAction("primary"); }} /> { this.props.setColorAction("info"); }} /> { this.props.setColorAction("success"); }} /> { this.props.setColorAction("warning"); }} /> { this.props.setColorAction("danger"); }} />
  • Download now
  • Documentation
  • Want more components?
  • Get pro version
); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

Let’s open the project again with npm start and see how everything works. Ta da!

Thanks for reading!

If you’ve enjoyed reading this tutorial please share it. I am very keen on hearing your thoughts about it. Just give this thread a comment and I’ll be more than happy to reply.

Special thanks should also go to Esther Falayi for his tutorial which has given me some much needed understanding on Redux.

Useful links:

  • Get the code for this tutorial from Github
  • Read more about ReactJS on their official website
  • Read more about Redux here
  • Read more about React-Redux
  • Check out our platform to see what we are doing and who we are
  • Get Paper Dashboard React from www.creative-tim.com or from Github
  • Read more about Reactstrap, the core of Paper Dashboard React

Find me on:

  • Email: [email protected]
  • Facebook: //www.facebook.com/NazareEmanuel
  • Instagram: //www.instagram.com/manu.nazare/
  • Linkedin: //www.linkedin.com/in/nazare-emanuel-ioan-4298b5149/