Original article: How to use Redux in ReactJS with real-life examples

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

Muchos de nuestros usuarios nos han preguntado a mi equipo o a mí si las plantillas creadas por mí tenían Redux . O si se crearon de tal manera que pudieran usarse con Redux. Y mi respuesta siempre era algo así como: “Todavía no he trabajado con Redux y no sé qué respuesta 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, agregaré Redux en uno de los proyectos en los que he trabajado durante el último y algunos años.

Es bueno saber antes de seguir adelante y pelearse con estas dos bibliotecas que:

Crear un nuevo proyecto basado en ReactJS y agregarle Redux

Lo primero es lo primero, creamos una nueva aplicación de react, cd en ella e iniciamos.

create-react-app react-redux-tutorial
cd react-redux-tutorial
npm start
nDaQRa3VplnG8gqJg08kGNkwpeSvlad2s30B
default create-react-app output of npm start

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

No os he dicho para qué vamos a usar Redux, o qué estamos haciendo aquí. Ha sido porque necesitaba la imagen gif de arriba.

Para que este tutorial sea ligero 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 girar.

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 crear un estado global para toda la aplicación, al que puede acceder cualquiera de sus componentes.
  • Es una biblioteca de gestión de estado.
  • 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 a esto.
  • Según la documentación oficial: Permite que sus componentes React lean datos de un almacén Redux y envíen acciones al almacén para actualizar datos

NOTA : Si tienes problemas con el comando anterior, intenta instalar los paquetes por separado

Cuando trabajes con Redux, necesitarás tres cosas principales:

  • acciones: Estos son objetos que deben tener dos propiedades, una que describe el tipo de acción y otra que describe lo que debe cambiarse 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.
  • almacén: Reúne las acciones y los reductores, mantiene y cambia el estado de toda la aplicación; solo hay un almacén.

Como he dicho antes, vamos a detener y comenzar a girar el logotipo 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
};

A continuación, vamos a decirle a nuestro reductor que el tipo de acción es sobre la rotación ( rotate ) del logo de React. Y el estado para la rotación del logotipo de React debe cambiarse a verdadero : Queremos que el logotipo comience a girar.

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

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

Ahora, vamos a decirle a nuestro reductor que el tipo de acción es sobre la rotación (rotate) del logo de React. Y el estado para la rotación del logotipo de React debe cambiarse a falso : Queremos que el logotipo deje de girar.

También creamos el reductor para nuestra aplicación:

1 — Comandos de Linux/Mac

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

2 — Comandos de Windows

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

Y, agregamos el siguiente código dentro de él:

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

Entonces, el reductor recibirá nuestras dos acciones, ambas del tipo rotar, y ambas cambian el mismo estado en la aplicación, que es state.rotating . Según la carga útil de estas acciones, state.rotating cambiará a true o false .

He agregado un caso predeterminado , que mantendrá el estado inalterado si el tipo de acción es no rotar . El valor predeterminado está allí en caso de que creemos una acción y olvidemos agregar un caso para esa acción. De esta manera, no eliminamos todo el estado de la aplicación, simplemente no hacemos nada y mantenemos lo que teníamos.

Lo último que debemos hacer es crear nuestro almacén para toda la aplicación. Dado que solo hay un almacén/un estado para toda la aplicación, no vamos a crear una nueva carpeta para el almacén. Si quieres, puedes crear una carpeta nueva para el almacén y agregarla allí, pero no es como con las acciones, por ejemplo, donde puedes tener múltiples acciones y parece mejor mantenerlas dentro de una carpeta.

Dicho esto, vamos a ejecutar este comando:

1 — Comando Linux/Mac

touch src/store.js

2 — Comando de Windows

echo "" > src\store.js

Y también agregamos el siguiente código dentro de él:

import { createStore } from "redux";
import rotateReducer from "reducers/rotateReducer";

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

export default configureStore;

Entonces, creamos una función llamada configureStore en la que enviamos un estado predeterminado, y creamos nuestro almacén usando el reductor creado y el estado predeterminado.

No estoy seguro si has visto mis importaciones, usan rutas absolutas, por lo que es posible que tengas algunos errores debido a esto. La solución para esto es una de las dos:

Cualquiera

1 — Agrega un archivo .env a tu aplicación así:

echo "NODE_PATH=./src" > .env

O

2 — Instala cross-env globalmente y cambia el script de inicio del archivo package.json así:

npm install -g cross-env

Y dentro del paquete.json

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

Ahora que hemos configurado nuestro almacén, nuestras acciones y nuestro reductor, necesitamos agregar una nueva clase dentro del archivo src/App.css . Esta clase pausará la animación giratoria del logo.

Así que vamos a escribir lo siguiente dentro de src/App.css :

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

Entonces, tu archivo App.css debería verse así:

.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);
  }
}

Ahora, solo necesitamos modificar nuestro archivo src/App.js para que escuche el estado de nuestro almacén. Y al hacer clic en el logotipo, llama a una de las acciones de inicio o detención.

Lo primero es lo primero, necesitamos conectar nuestro componente a nuestro almacén redux para importar la conexión desde react-redux .

import { connect } from "react-redux";

Después de esto, exportaremos nuestro componente de aplicación a través del método de conexión de esta manera:

export default connect()(App);

Para cambiar el estado del almacén redux, necesitaremos las acciones que hemos realizado anteriormente, así que importémoslas también:

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

Ahora necesitamos recuperar el estado de nuestro almacén y decir que queremos que las acciones de inicio y detención se usen para cambiar el estado.

Esto se hará usando la función de conexión, que acepta dos parámetros:

  • mapStateToProps: Este se usa para recuperar el estado del almacén
  • mapDispatchToProps: Este se usa para recuperar las acciones y enviarlas al almacén

Puedes leer más sobre ellos aquí: conectar argumentos de función de react-redux.

Ahora, escribamos dentro de nuestro App.js (al final del archivo, si puedes):

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

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

Después de esto, agreguémoslos dentro de nuestra función de conexión así:

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

Y ahora ya, dentro de nuestro componente App, podemos acceder al estado del almacén, startAction y stopAction a través de props.

Cambiemos la etiqueta img a:

<img 
  src={logo} 
  className={
    "App-logo" + 
    (this.props.rotating ? "":" App-logo-paused")
  } 
  alt="logo" 
  onClick={
    this.props.rotating ? 
      this.props.stopAction : this.props.startAction
  }
/>

Aquí, lo que estamos diciendo es que, si el estado de rotación del almacén (this.props.rotating ) es verdadero, entonces solo queremos que la className App-logo se establezca en nuestra img . Si eso es falso, también queremos que la clase App-logo-paused se establezca en className . De esta manera pausamos la animación.

Además, si this.props.rotating es verdadero , luego queremos enviar a nuestro almacén por la función onClick y volver a cambiarlo a falso , y viceversa.

Casi hemos terminado, pero nos hemos olvidado de algo.

Todavía no le hemos dicho a nuestra aplicación de react que tenemos un estado global o, si lo prefieres, que usamos la administración de estado redux.

Para ello, ingresamos a src/index.js , importamos un Provider de react-redux y el almacén recién creado así:

import { Provider } from "react-redux";

import configureStore from "store";
  • Provider : Hace que el almacén Redux esté disponible para cualquier componente anidado que se haya incluido en la función de conexión

Después de esto, en lugar de renderizar nuestro componente App directamente, lo renderizamos a través de nuestro Provider usando el almacén que hemos creado así:

ReactDOM.render(
  <Provider store={configureStore()}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Aquí podríamos haber usado la función configureStore con algún otro estado, por ejemplo configureStore({rotating: false }) .

Entonces, el index.js debería verse así:

import React from 'react';
import ReactDOM from 'react-dom';
// new imports start (Inicio nuevas importaciones)
import { Provider } from "react-redux";

import configureStore from "store";
// new imports stop (Fin de nuevas importaciones)

import './index.css';

import App from './App';
import * as serviceWorker from './serviceWorker';

// changed the render (Cambiado el render)
ReactDOM.render(
  <Provider store={configureStore()}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// changed the render (Cambiado el render)

serviceWorker.unregister();

Avancemos y veamos si nuestra aplicación redux funciona:

cOdx8xHzZjMmqEYSTgVkkPSXkG925Hwewoxj
react and redux in action

Uso de creadores de acciones

Opcionalmente, en lugar de acciones , podemos usar creadores de acciones , que son funciones que crean acciones.

De esta forma, podemos combinar nuestras dos acciones en una sola función y reducir un poco nuestro código.

Entonces, sigamos adelante y creemos un nuevo archivo:

1 — Comando Linux/Mac

touch src/actions/rotateAction.js

2 — Comando de Windows

echo "" > src\actions\rotateAction.js

Y añadimos este código:

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

Vamos a enviar una acción de tipo rotar, con un payload que vamos a obtener en el componente App.

Dentro del componente src/App.js, necesitamos importar nuestro nuevo creador de acciones:

import rotateAction from "actions/rotateAction";

Agregamos la nueva función a mapDispatchToProps así:

rotateAction: Recibirá una (payload) y enviará la acción rotateAction con la payload

Cambiamos la función onClick a:

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

Y finalmente, agregamos nuestro nuevo creador de acciones al mapDispatchToProps de esta manera:

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

También podemos eliminar las importaciones antiguas para las acciones antiguas y eliminarlas también de mapDispatchToProps .

Así es como debería verse el nuevo src/App.js:

import React, { Component } from 'react';
// new lines from here (Nuevas líneas desde aquí)
import { connect } from "react-redux";
import rotateAction from "actions/rotateAction";

//// new lines to here (Nuevas líneas hasta aquí)

import logo from './logo.svg';
import './App.css';

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)
            }
          />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

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

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

Un ejemplo de la vida cotidiana con Paper Dashboard React

356UctzmEu8euFGJLpMVitFUDLy3LduQQTl4
Paper Dashboard React — Product Gif

Como puedes ver en la imagen gif de arriba, estoy usando el menú de la derecha para cambiar los colores del menú de la izquierda. Esto se logra mediante el uso de estados de componentes y pasando ese estado de un componente principal a los dos menús y algunas funciones para cambiar ese estado.

V3KhG508UOWnU1CA2FFtKEACx7vU3BRyDfyQ
small diagram on how the app works at the moment

Pensé que sería un buen ejemplo tomar este producto y reemplazar los estados de los componentes con Redux.

Puedes conseguirlo de estas 3 formas:

  1. Descargar desde creative-tim.com
  2. Descargar desde Github
  3. Clonar de Github:
git clone https://github.com/creativetimofficial/paper-dashboard-react.git

Ahora que tenemos este producto, vamos a entrar en el directorio e instalar de nuevo redux y react-redux:

npm install --save redux react-redux

Después de esto, necesitamos crear las acciones. Dado que en el menú de la derecha tenemos 2 colores que configuran el fondo del menú de la izquierda y 5 colores que cambian el color de los enlaces, necesitamos 7 acciones o 2 creadores de acciones, y vamos con esta segunda opción ya que requiere un poco menos de código:

1 — Comandos de Linux/Mac

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

2 — Comandos de Windows

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

Después de esto, vamos a crear el código de las acciones de la siguiente manera:

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;

Ahora, como en la primera parte, necesitamos el reductor:

1 — Comandos de Linux/Mac

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

2 — Comandos de Windows

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

Y el código para el reductor:

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

Cómo puedes ver aquí, a diferencia de nuestro primer ejemplo, queremos mantener nuestro estado anterior y actualizar su contenido.

También necesitamos el almacén:

1 — Comando Linux/Mac

touch src/store.js

2 — Comando de Windows

echo "" > src\store.js

El código para ello:

import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";

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

Dentro de src/index.js necesitamos:

// new imports start (Inicio de nuevas importaciones)
import { Provider } from "react-redux";

import configureStore from "store";
// new imports stop (Fin de nuevas importaciones)

Y también, cambia la función de renderizado :

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")
);

Entonces, el archivo index.js debería verse así:

import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
import { Router, Route, Switch } from "react-router-dom";
// new imports start (Inicio de nuevas importaciones)
import { Provider } from "react-redux";

import configureStore from "store";
// new imports stop (Fin de nuevas importaciones)

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(
  <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")
);

Ahora necesitamos hacer algunos cambios dentro de src/layouts/Dashboard/Dashboard.jsx . Necesitamos eliminar el estado y las funciones que cambian el estado. Así que adelante, elimina estos fragmentos de código:

El constructor (entre las líneas 16 y 22):

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

Las funciones de estado (entre las líneas 41 y 46):

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

Los accesorios bgColor y activeColor de la barra lateral (líneas 53 y 54):

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

Todos los accesorios de FixedPlugin (entre las líneas 59–62):

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

Entonces, nos quedamos con este código dentro del componente de diseño del Dashboard (Panel de control):

import React from "react";
// javascript plugin used to create scrollbars on windows
// plugin de javascript que se usa para crear barras de desplazamiento en 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 (
      <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;

Ahora necesitamos conectar los componentes Sidebar y FixedPlugin al almacén.

En src/components/Sidebar/Sidebar.jsx :

import { connect } from "react-redux";

Y cambiar la exportación (export) a:

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

export default connect(mapStateToProps)(Sidebar);

En src/components/FixedPlugin/FixedPlugin.jsx :

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

La exportación ahora debería ser:

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

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

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

Y Vamos a tener estos próximos cambios:

  • en cualquier lugar donde encuentres la palabra handleBgClick , debemos cambiarla a setBgAction
  • en cualquier lugar donde encuentres la palabra handleActiveClick , debemos cambiarla a setColorAction

Entonces, el componente FixedPlugin ahora debería verse así:

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 (
      <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="header-title">SIDEBAR BACKGROUND</li>
            <li className="adjustments-line">
              <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 className="header-title">SIDEBAR ACTIVE COLOR</li>
            <li className="adjustments-line">
              <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>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react"
                color="primary"
                block
                round
              >
                Download now
              </Button>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react/#/documentation/tutorial"
                color="default"
                block
                round
                outline
              >
                <i className="nc-icon nc-paper"></i> Documentation
              </Button>
            </li>
            <li className="header-title">Want more components?</li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-pro-react"
                color="danger"
                block
                round
                disabled
              >
                Get pro version
              </Button>
            </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);

Y hemos terminado, puedes iniciar el proyecto y ver cómo funciona todo bien:

Pxtk6P8ssePiK2LaAmOuG4tvn8SCXwJPVKs3

Reductores múltiples

Como puedes tener múltiples acciones, puedes tener múltiples reductores. Lo único que necesitas es combinarlos, lo veremos un poco más abajo.

Avancemos y creemos dos nuevos reductores para nuestra aplicación, uno para setBgAction y otro para setColorAction :

1 — Comandos de Linux/Mac

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

2 — Comandos de Windows

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

Después de esto, vamos a crear el código de los reductores de la siguiente manera:

src/reductores/bgReducer.js

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

src/reductores/colorReducer.js

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

Cuando trabajes con reductores combinados, debes agregar un estado predeterminado en cada uno de los reductores que se van a combinar. En mi caso, he elegido un objeto vacío, es decir, state = {} ;

Y ahora, nuestro rootReducer combinará a estos dos de la siguiente manera:

src/reductores/rootReducer.js

import { combineReducers } from 'redux';

import bgReducer from 'reducers/bgReducer';
import colorReducer from 'reducers/colorReducer';

export default combineReducers({
  activeState: colorReducer,
  bgState: bgReducer
});

Por lo tanto, decimos que queremos que colorReducer sea referido por la propiedad activeState del estado de la aplicación, y bgReducer sea referido por la propiedad bgState del estado de la aplicación.

Esto significa que nuestro estado ya no se verá así:

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

Sinó que ahora se verá así:

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

Como hemos cambiado nuestros reductores, ahora los hemos combinado en uno sólo, y también necesitamos cambiar nuestro store.js :

src/store.js

import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";

// we need to pass the initial state with the new look
// necesitamos pasar el estado (state) inicial con la nueva apariencia
function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) {
  return createStore(rootReducer,state);
}
export default configureStore;

Dado que hemos cambiado la apariencia del estado, ahora debemos cambiar los accesorios dentro de los componentes Sidebar y FixedPlugin al nuevo objeto de estado:

src/components/Sidebar/Sidebar.jsx :

Cambia la línea 36 de

<div className="sidebar" data-color={this.props.bgColor} data-active-color={this.props.activeColor}>

a

<div className="sidebar" data-color={this.props.bgState.bgColor} data-active-color={this.props.activeState.activeColor}>

src/components/FixedPlugin/FixedPlugin.jsx :

Necesitamos cambiar todos los this.props.bgColora this.props.bgState.bgColor . Y todo el this.props.activeColora this.props.activeState.activeColor.

Así que el nuevo código debería verse así:

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 (
      <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="header-title">SIDEBAR BACKGROUND</li>
            <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="header-title">SIDEBAR ACTIVE COLOR</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>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react"
                color="primary"
                block
                round
              >
                Download now
              </Button>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react/#/documentation/tutorial"
                color="default"
                block
                round
                outline
              >
                <i className="nc-icon nc-paper"></i> Documentation
              </Button>
            </li>
            <li className="header-title">Want more components?</li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-pro-react"
                color="danger"
                block
                round
                disabled
              >
                Get pro version
              </Button>
            </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);

Abramos el proyecto nuevamente con npm start y veamos cómo funciona todo. ¡Tachán!

¡Gracias por leer!

Si te ha gustado leer este tutorial, compártelo. Estoy muy interesado en escuchar tus pensamientos al respecto. Simplemente deja un comentario en este hilo y estaré más que feliz de responder.

Un agradecimiento especial también debe ir a Esther Falayi por su tutorial que me ha brindado una comprensión muy necesaria sobre Redux .

Enlace útil:

  • Obtén el código para este tutorial de Github

Encuéntrame en: