Artigo original escrito por: Nazare Emanuel Ioan
Artigo original: How to use Redux in ReactJS with real-life examples
Traduzido e adaptado por: Rafael Fontenelle

Desde que comecei a trabalhar com o ReactJS, na Creative-Tim, só o usei para criar aplicações simples do react, ou templates, se preferir. Eu usei o ReactJS apenas com o create-react-app e nunca tentei integrá-lo com algo mais.

Muitos de nossos usuários perguntaram a mim ou à minha equipe se os templates criados por mim tinham Redux neles. Ou se eles foram criados de forma que pudessem ser usados com o Redux. E minha resposta sempre foi algo como: "Ainda não trabalhei com Redux e não sei qual resposta devo dar a você".

‌‌Então, aqui estou eu agora, escrevendo um artigo sobre Redux e como ele deve ser usado no React. Mais adiante, neste artigo, vou adicionar o Redux em cima de um dos projetos que tenho trabalhado no último ano ou mais.

‌‌É bom saber antes de prosseguirmos e lutarmos com essas duas bibliotecas que:

Criando um projeto baseado no ReactJS e adicionando o Redux a ele

Primeiramente, vamos criar uma aplicação do react, entrar nela com cd e iniciá-la.

create-react-app react-redux-tutorial
cd react-redux-tutorial
npm start
nDaQRa3VplnG8gqJg08kGNkwpeSvlad2s30B
Saída padrão do npm start no create-react-app

Como podemos ver, o create-react-app nos dá um template bem básico com um parágrafo, um elemento de âncora para o site do React e o ícone oficial do ReactJS girando.

Eu não contei a vocês para que vamos usar o Redux, ou o que estamos fazendo aqui. E isso é porque eu precisava da imagem em formato gif acima.

Para tornar este artigo de tutorial leve e fácil de entender, não vamos construir algo muito complexo. Vamos usar o Redux para fazer a imagem do React acima parar ou começar a girar.

Dito isso, vamos adicionar os seguintes pacotes do Redux:

npm install --save redux react-redux

redux v4.0.1

  • O que o Redux faz, de forma geral, é criar um estado global para toda a aplicação, que pode ser acessado por qualquer um de seus componentes
  • É uma biblioteca de gerenciamento de estado
  • Você tem apenas um estado para toda sua aplicação, e não estados para cada um de seus componentes

react-redux v5.1.1

  • Ele é usado para que possamos acessar os dados do Redux e modificá-los enviando ações para o Redux – na verdade não é o Redux, mas chegaremos lá
  • A documentação oficial afirma: ele permite que seus componentes do React leiam dados de um armazenamento Redux e enviem ações para o armazenamento para atualizar dados

OBSERVAÇÃO: se você tiver problemas com o comando acima, tente instalar os pacotes separadamente

Ao trabalhar com o Redux, você precisará de três coisas principais:

  • ações (actions): são objetos que devem ter duas propriedades, uma descrevendo o tipo de ação e outra descrevendo o que deve ser alterado no estado da aplicação.
  • redutores (reducers): são funções que implementam o comportamento das ações. Eles alteram o estado da aplicação, com base na descrição da ação e na descrição da alteração do estado.
  • armazenamento (store): reúne as ações e os redutores, mantendo e alterando o estado de toda a aplicação – há apenas um armazenamento.

Como eu disse acima, vamos parar e começar a girar o logotipo do React. Isso significa que vamos precisar de duas ações, da seguinte forma:

1 — Comandos no Linux / Mac

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

2 — Comandos no Windows

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

Agora, vamos editar o src/actions/startAction.js da seguinte forma:

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

Então, vamos dizer ao nosso redutor que o tipo de ação é sobre a rotação (rotate) do logotipo do React. E o estado para a rotação do logotipo do React deve ser alterado para true – queremos que o logotipo comece a girar.

Agora vamos editar o src/actions/stopAction.js da seguinte forma:

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

Então, vamos dizer ao nosso redutor que o tipo de ação tem a ver com a rotação (rotate) do logotipo do React. E o estado para a rotação do logotipo do React deve ser alterado para false – queremos que o logotipo pare de girar.

Vamos também criar o redutor para nossa aplicação:

1 — Comandos no Linux / Mac

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

2 — Comandos no Windows

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

e adicionar o seguinte código dentro dele:

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

Assim, o redutor receberá ambas as nossas ações, ambas do tipo rotate, e ambas alteram o mesmo estado na aplicação – que é state.rotating. Com base na carga dessas ações, state.rotating mudará para true ou false.

Eu adicionei um caso padrão (default), que manterá o estado inalterado se o tipo de ação não for rotate. O valor padrão está lá no caso de criarmos uma ação e esquecermos de adicionar um caso para essa ação. Dessa forma, não excluímos todo o estado da aplicação – simplesmente não fazemos nada e mantemos o que tínhamos.

A última coisa que precisamos fazer é criar nosso armazenamento (store) para toda a aplicação. Como há apenas um armazenamento/um estado (state) para toda a aplicação, não criaremos uma pasta para o armazenamento. Se quiser, você pode criar uma pasta para o armazenamento e adicioná-lo lá, mas não é como com as ações, por exemplo, onde você pode ter várias ações e fica melhor mantê-las dentro de uma pasta.

Então, dito isso, vamos executar este comando:

1 — Comando no Linux / Mac

touch src/store.js

2 — Comando no Windows

echo "" > src\store.js

E também adicionar o seguinte código dentro dele:

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

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

export default configureStore;

Então, criamos uma função chamada configureStore, na qual enviamos um estado padrão e criamos nosso armazenamento usando o redutor criado e o estado padrão.

Não tenho certeza se você viu minhas que importações usam caminhos absolutos. Você poderá ter alguns erros devido a isso. A correção para isso é uma das duas:

1 — Adicionar um arquivo .env em sua aplicação assim:

echo "NODE_PATH=./src" > .env

Ou

2 — Instalar cross-env globalmente e alterar o script de inicialização do arquivo package.json desta forma:

npm install -g cross-env

E dentro do package.json

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

Agora que configuramos nosso armazenamento, nossas ações e nosso redutor, precisamos adicionar uma nova classe dentro do arquivo src/App.css. Essa classe pausará a animação rotativa do logotipo.

Então, vamos escrever o seguinte dentro de src/App.css:

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

Seu arquivo App.css deverá se parecer com algo assim:

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

Agora, precisamos apenas modificar nosso arquivo src/App.js para que ele escute o estado de nosso armazenamento. Ao clicar no logotipo, ele chama uma das ações de iniciar ou parar.

Primeiramente, precisamos conectar nosso componente ao nosso armazenamento  do Redux para que possamos importar connect do react-redux.

import { connect } from "react-redux";

Depois disso, exportamos nosso componente App por meio do método connect desta forma:

export default connect()(App);

Para alterar o estado do armazenamento do Redux, precisamos das ações que nós criamos anteriormente. Então, vamos importá-las também:

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

Agora, precisamos recuperar o estado de nosso armazenamento e dizer que queremos que as ações start e stop sejam usadas para alterar o estado.

Isso será feito usando a função connect, que aceita dois parâmetros:

  • mapStateToProps: é usado para recuperar o estado do armazenamento
  • mapDispatchToProps: é usado para recuperar as ações e despachá-las para o armazenamento

Você pode ler mais sobre eles aqui: argumentos da função connect do react-redux.

Então, vamos escrever dentro de nosso App.js (no fim do arquivo, se você quiser):

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

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

Depois disso, vamos adicioná-los dentro de nossa função connect desta forma:

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

E agora mesmo, dentro de nosso componente App, podemos acessar o estado do armazenamento, o startAction e o stopAction por meio de props.

Vamos alterar a tag img para:

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

Então, o que estamos dizendo aqui é que, se o estado de armazenamento da rotação (this.props.rotating) for true, queremos apenas que o className do App-logo seja definido com nosso img. Caso seja false, também queremos que a classe App-logo-paused seja definida no className. Desta forma, pausamos a animação.

Além disso, se this.props.rotating for true, queremos enviar para nosso armazenamento a função onClick e alterá-la novamente para false e vice-versa.

Estamos quase terminando, mas esquecemos algo.

Ainda não dissemos à nossa aplicação do React que temos um estado global ou, se você quiser, que usamos o gerenciamento de estado do redux.

Para isso, entramos no src/index.js, importamos um Provider do react-redux e o armazenamento recém-criado desta forma:

import { Provider } from "react-redux";

import configureStore from "store";
  • Provider: disponibiliza o armazenamento Redux para qualquer componente aninhado que esteja envolto pela função connect

Depois disso, em vez de renderizar nosso componente App diretamente, vamos renderizá-lo por meio de nosso Provider, usando o armazenamento que criamos, desta forma:

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

Aqui, poderíamos ter usado a função configureStore com outro estado. Por exemplo, configureStore({ rotating: false }).

Então, seu index.js deve se parecer com isso:

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(
  <Provider store={configureStore()}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// changed the render

serviceWorker.unregister();

Agora, vamos seguir em frente e ver se nossa aplicação com Redux funciona:

cOdx8xHzZjMmqEYSTgVkkPSXkG925Hwewoxj
React e Redux em ação

Usando criadores de ação

Como opção, em vez de ações, poderíamos usar criadores de ação (action creators), que são funções que criam ações.

Desta forma, combinamos nossas duas ações em apenas uma função e reduzimos um pouco o nosso código.

Então, sigamos em frente e vamos criar um outro arquivo:

1 — Comando no Linux / Mac

touch src/actions/rotateAction.js

2 — Comando no Windows

echo "" > src\actions\rotateAction.js

E adicionar este código:

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

Vamos enviar uma ação do tipo rotate, com uma carga útil que vamos obter no componente App.

Dentro do componente src/App.js, precisamos importar nosso criador de ação:

import rotateAction from "actions/rotateAction";

Adicione a nova função ao mapDispatchToProps desta forma:

rotateAction: vai receber uma (carga útil) e vai despachar o rotateAction com a carga útil

Altere a função onClick para:

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

e, finalmente, adicione nosso novo criador de ação para mapDispatchToProps desta forma:

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

Também podemos excluir as importações antigas para as ações antigas e excluí-las do mapDispatchToProps também.

É assim que seu novo src/App.js deve se parecer:

import React, { Component } from 'react';
// linhas novas a partir daqui
import { connect } from "react-redux";
import rotateAction from "actions/rotateAction";

//// linhas novas até aqui

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

Um exemplo da vida real com Paper Dashboard React

356UctzmEu8euFGJLpMVitFUDLy3LduQQTl4
Paper Dashboard React — Gif do produto

Como você verá na imagem gif acima, estou usando o menu direito para alterar as cores do menu à esquerda. Isso é obtido usando estados de componentes e passando esse estado de um componente pai para os dois menus e algumas funções para alterar esse estado.

V3KhG508UOWnU1CA2FFtKEACx7vU3BRyDfyQ
Pequeno diagrama com o funcionamento da aplicação no momento

Eu pensava que esse seria um exemplo legal, pegar este produto e substituir os estados de componente com Redux.

Você pode obter isso nestas 3 formas:

  1. Baixar de creative-tim.com
  2. Baixar do GitHub
  3. Clonar do GitHub:
git clone https://github.com/creativetimofficial/paper-dashboard-react.git

Agora que temos este produto, vamos entrar nele com cd e instalar novamente o redux e o react-redux:

npm install --save redux react-redux

Depois disso, precisamos criar as ações. Como, no menu direito, temos 2 cores que definem o fundo do menu esquerdo e 5 cores que alteram a cor dos links, precisamos de 7 ações, ou 2 criadores de ações – usaremos essa segunda opção, pois é um pouco menos de código para escrever:

1 — Comandos no Linux / Mac

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

2 — Comandos no Windows

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

Depois disso, vamos criar o código das ações da seguinte forma:

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;

Agora, como na primeira parte, precisamos do redutor:

1 — Comandos no Linux / Mac

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

2 — Comandos no Windows

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

E o código para o redutor:

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

Como você pode ver aqui, diferentemente de nosso primeiro exemplo, queremos manter nosso estado antigo e atualizar seu conteúdo.

Também precisamos do armazenamento:

1 — Comando no Linux / Mac

touch src/store.js

2 — Comando no Windows

echo "" > src\store.js

E o código para ele:

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

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

Dentro do src/index.js, precisamos de:

// Início dos novos imports
import { Provider } from "react-redux";

import configureStore from "store";
// Fim dos novos imports

e também altere a função render:

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

Então, o arquivo index.js deve se parecer com isso:

import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
import { Router, Route, Switch } from "react-router-dom";
// Início dos novos imports
import { Provider } from "react-redux";

import configureStore from "store";
// Fim dos novos imports

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

Agora, precisamos fazer algumas alterações dentro de src/layouts/Dashboard/Dashboard.jsx. Precisamos excluir o estado e as funções que alteram o estado. Então, vá em frente e exclua esses pedaços de código:

O construtor (entre as linhas 16 e 22):

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

As funções de estado (entre as linhas 41 e 46):

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

As propriedades bgColor e activeColor da barra lateral (linhas 53 e 54):

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

Todas as propriedades do FixedPlugin (entre as linhas 59 e 62):

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

Então, ficamos com este código dentro do componente de leiuate do Dashboard:

import React from "react";
// plugin do javascript usado para criar as barras de rolagem nas janelas
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;

Vamos precisar conectar os componentes Sidebar e FixedPlugin ao armazenamento.

Para o src/components/Sidebar/Sidebar.jsx:

import { connect } from "react-redux";

E alterar a exportação para:

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

export default connect(mapStateToProps)(Sidebar);

Para o src/components/FixedPlugin/FixedPlugin.jsx:

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

A exportação agora deve ser:

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

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

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

Vamos ter as alterações a seguir:

  • todo lugar em que você encontrar a palavra handleBgClick, você vai precisar alterá-la para setBgAction
  • todo lugar em que você encontrar a palavra handleActiveClick, você vai precisar alterá-la para setColorAction

Então, o componente FixedPlugin deve se parecer com isso:

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

E pronto, você pode iniciar o projeto e ver se tudo funciona bem:

Pxtk6P8ssePiK2LaAmOuG4tvn8SCXwJPVKs3

Vários redutores

Assim como você pode ter várias ações, você pode ter vários redutores. A única questão é que você precisa combiná-los – você vai ver isso um pouco mais abaixo.

Agora, vamos seguir e criar outros dois redutores para nossa aplicação, uma para o setBgAction e uma para o setColorAction:

1 — Comandos no Linux / Mac

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

2 — Comando no Windows

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

Depois disso, vamos criar o código dos redutores da seguinte forma:

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

Ao trabalhar com redutores combinados, você precisa adicionar um estado padrão em cada um de seus redutores que serão combinados. No meu caso, eu escolho um objeto vazio, ou seja, state = {};

E agora, nosso rootReducer vai combinar esses dois da seguinte forma:

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

Então, informamos que queremos que o colorReducer seja referenciado pela propriedade activeState do estado da aplicação, e que o bgReducer seja referenciado pela propriedade bgState do estado da aplicação.

Isso significa que nosso estado não vai mais se parecer com isso:

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

Agora, ele vai se parecer com isso:

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

Já que alteramos nossos redutores, agora vamos combiná-los em apenas um, então precisamos alterar nosso store.js também:

src/store.js

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

// precisamos passar o estado inicial com a nova aparência
function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) {
  return createStore(rootReducer,state);
}
export default configureStore;

Já que alteramos a aparência do estado, precisamos alterar as propriedades dentro dos componentes Sidebar e FixedPlugin para o novo objeto de estado:

src/components/Sidebar/Sidebar.jsx:

Altere a linha 36 de

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

para

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

src/components/FixedPlugin/FixedPlugin.jsx:

Precisamos alterar todos os this.props.bgColor para this.props.bgState.bgColor e todos os this.props.activeColor para this.props.activeState.activeColor.

Então, o novo código deve se parecer com isso:

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

Vamos abrir o projeto novamente com npm start e ver se tudo funciona. Aí está!

Obrigado pela leitura!

Se você gostou de ler este tutorial, compartilhe. Estou muito interessado em ouvir seus pensamentos sobre isso. Basta dar um comentário a este tópico e terei o maior prazer em responder.

Agradecimentos especiais também devem ir para Esther Falayi por seu tutorial (em inglês) que me deu uma compreensão muito necessária sobre Redux.

Links úteis:

Encontre o autor original deste artigo em: