Entendendo Redux com ReactJS
Tags: redux, react, javascript, tutorial
Published: 2022-06-16 00:27:58

## Introdução

Aplicações modernas de front-end são constantemente controladas por estados que provocam renderizações e definem os comportamentos das suas telas. É comum termos que **compartilhar estados** entre vários componentes. Entretanto, em aplicações maiores, a comunicação desses estados entre os componentes começa a se tornar mais **complexa**, visto que, muitas vezes, precisaremos compartilhá-los por meio de **props** entre **componentes distantes**, fazendo com que todos os outros componentes que ligam esses dois tenham acesso a esses estados, sem necessariamente precisarem deles.
  
Este problema resulta em **dificuldades de leitura e manutenção do código**, tornando-o extremamente acoplado, com componentes dependentes uns dos outros. Este comportamento de compartilhar estados entre diversos componentes que não precisam dos mesmos, apenas por estarem no caminho para o componente final, é conhecido como **Prop Drilling**.

![Exemplo de Prop Drilling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdfxa55pvulr7rtmch0d.png)
<figcaption>Ilustração do Prop Drilling. Fonte: Alura</figcaption>
  
##Como resolver o Prop Drilling?


Neste artigo, utilizaremos um gerenciador de **estados globais** conhecido como **Redux**. Ele utiliza de um conceito chamado de **Store** para salvar todos os estados que você precisar em um único lugar que pode ser obtido a qualquer momento, em qualquer parte da sua aplicação.

![Store fornecendo os estados para todos os componentes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/am3ac9kilqo1ge2cjved.png)
<figcaption>Store fornecendo os estados para todos os componentes</figcaption>

##Como podemos começar a usar?

Criaremos um projeto em **ReactJS** que funcionará como uma lista de tarefas, onde poderemos adicionar uma nova _string_ ao final de um _array_ utilizando um **input** e um **botão**.

De início, inicie seu projeto com;
`yarn create react-app projeto-redux`
ou
`npx create-react-app projeto-redux`

E instale as bibliotecas que serão necessárias:
`cd projeto-redux`
`yarn add @reduxjs/toolkit redux react-redux`
ou 
`npm install @reduxjs/toolkit redux react-redux`

Lembre-se sempre de consultar a [documentação oficial](https://redux.js.org/introduction/installation) para conferir se houve alguma atualização.
  
Com as bibliotecas instaladas, daremos início à organização de pastas e arquivos. Recomendo criar um `index.js` dentro da pasta `store`, que também será criada dentro da pasta `src` do projeto.


![Imagem de onde o arquivo index está localizado, dentro da pasta src e, depois, store](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yue1hbfc8w8pbtnta9l3.png)
<figcaption>Organização dos arquivos do projeto</figcaption>

Em seguida, iremos **criar a nossa Store**, iniciando-a apenas com a estrutura que será utilizada.

```js
// src/store/index.js

import { configureStore } from "@reduxjs/toolkit";

export const store = configureStore();
```

Para que toda a aplicação tenha acesso à Store com nossos estados, iremos englobar todo o _app_ dentro de um componente que o React-Redux nos proporciona chamado **Provider**, que requer uma _prop_ que será justamente a instância da Store que acabamos de criar.

```js
//index.js

import React from "react";
import ReactDOM from "react-dom/client";
// Redux config
import { Provider } from "react-redux";
import { store } from "./store";

import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    /*
      O Provider é o responsável por disponibilizar a Store para 
      toda a aplicação
    */
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

```


##E agora, como utilizar a Store?

Por meio das **Actions** e **Reducers** que o Redux disponibiliza.


![Fluxo de atividade do Redux](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/td372pthqwrb5pv3eod2.png)
<figcaption>Fluxo de eventos do Redux</figcaption>


As **Actions** são funções que serão executadas e seu retorno será utilizado pelos **Reducers** para atualizarmos os nossos estados da Store. Sendo assim, é onde entrará qualquer lógica e requisição Http que queira fazer. 
  
Ao final da execução da sua Action é necessário que, no seu retorno, seja disponibilizado um objeto com os **valores que serão salvos** no estado e um atributo `type`, sendo ele uma _string_ com um **valor único** para cada Action, que será utilizado pelos Reducers como um identificador. 
  
Vamos, então, criar o nosso arquivo `action.js` dentro da pasta store, junto ao nosso arquivo `index.js` anteriormente criado.
  
Essa Action receberá o nome da nova tarefa como parâmetro e retornará um objeto com o seu _type_ único e a tarefa que será salva.

```js
// src/store/actions.js

export function addTask(newTask) {
  return {
    type: 'ADD_TASK',
    newTask
  }
}
```

Os **Reducers** são funções que utilizarão do retorno das Actions 
como parâmetros para salvar os estados na Store. Ao invés de executarmos o Reducer como uma função comum, eles estão sempre **ouvindo todas as Actions** que estão sendo chamadas e, então, os Reducers identificarão o que fazer a partir de cada Action executadas. Como isso ocorre? A partir do atributo `type` que é retornado de todas as Actions. Se temos uma Action com `type: "ADD_TASK"`, então teremos um Reducer que irá tomar uma ação a partir dessa _string_.

```js
function myNewReducer(state, action) {
  /*
    switch(action.type) {
      case "ADD_TASK":
        // retornar o estado com o novo array atualizado
    }
  */
}
```
  
Os Reducers sempre receberão 2 parâmetros: `state`, onde teremos os estados atuais da Store; e `action`, onde teremos todos os atributos retornados pela Action que foi executada.
  
Utilizando de uma condicional como o `switch` para definirmos qual ação será tomada por cada `type`, vamos utilizar o retorno da Action para definir como será o novo estado.
  
Da mesma forma que com as Actions, criaremos um `reducers.js` onde guardaremos todos os Reducers da nossa aplicação. O nome da função de cada Reducer será o nome do atributo como será salvo no objeto da nossa Store - se criarmos um Reducer chamado `tasks`, acessaremos esse valor futuramente como `state.tasks`. 
  
Podemos, também, definir um estado inicial para nosso Reducer, para definirmos qual valor aquele estado terá antes de qualquer Action ser executada. Nesse caso, queremos que a lista de tarefas seja apenas um _array_ vazio, que será preenchido com as tarefas que virão da Action.

```js
// src/store/reducers.js

import { combineReducers } from "redux";

const initialState = { taskList: [] };

function tasks(state = initialState, action) {
  switch (action.type) {
    case "ADD_TASK":
      return { ...state, taskList: [...state.taskList, action.newTask] };
    default:
      return { ...state };
  }
}

export default combineReducers({
  tasks,
});
```
Uma atenção especial para a sintaxe de **como retornar o novo estado**. Ele deverá utilizar os 3 pontos `...` (chamados de **spread operator**) para copiarmos o estado atual, e depois alteramos apenas o que queremos. Dessa forma, o Redux identifica que houve uma alteração na Store e evita problemas de componentes não recebendo o estado atualizado. Detalhes mais profundos podem ser encontrados na [documentação oficial](https://redux.js.org/faq/immutable-data).
  
Para uma melhor organização do código, unimos todos os Reducers em um único objeto usando o `combineReducers()` que será consumido pela Store.
  
A partir daqui, essa será nossa Store:

```js
// src/store/index.js

import { configureStore } from "@reduxjs/toolkit";
import reducers from "./reducers";

export const store = configureStore({ reducer: reducers });

```

## Como unir esse fluxo com a nossa View?

Por meio do `dispatch` para executar as Actions e do `selector`(também chamado de subscribe) para acessarmos a Store e resgatarmos os estados que quisermos, para poder controlarmos as renderizações em tela.

![Fluxo de atividade do Redux](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00f5f7lg7pavecewjcff.png)
<figcaption>Revisão do fluxo do Redux</figcaption>

Para começarmos a utilizar o `dispatch`, utilizaremos o Hook `useDispatch` disponibilizado pela biblioteca React-Redux que instalamos, e importaremos a Action que criamos, como no exemplo abaixo.

```js
import { useDispatch } from "react-redux";
import { addTask } from './actions'

const dispatch = useDispatch();

dispatch(addTask('Prepare some coffee'))

```
Respeitando as [regras do Hooks](https://pt-br.reactjs.org/docs/hooks-rules.html), a condição para utilizarmos o Hook do `useDispatch` é que utilizemos o mesmo dentro de um **Componente Funcional**.

Para nossa aplicação, criaremos um componente de Home para testarmos nosso fluxo. Ele será um arquivo `index.js` dentro da pasta `Home`, que será o nome do nosso componente, e está dentro de uma pasta própria para páginas chamada `pages`, a fim de melhor organização dos arquivos.

![Estrutura de pastas ilustrando o componente de Home sendo criado dentro de src e pages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gog1jgds4zaqgn1zohm2.png)
<figcaption>Organização das pastas do projeto</figcaption>

Iniciaremos o componente apenas exportando-o e retornando uma _tag_ `div`.

```js
// src/pages/Home/index.js

import React from "react";

function Home() {
  return <div />;
}

export default Home;
```

Finalizamos com a importação do componente na raíz do nosso projeto, no arquivo `App.js`, que ficará da seguinte forma:

```js
// App.js
import Home from "./pages/Home";

function App() {
  return <Home />;
}

export default App;

```
Agora que podemos respeitas a regra de uso de um Hook dentro de um componente funcional, daremos início à importação do `useDispatch` disponibilizado pela biblioteca React-Redux para adicionarmos uma nova tarefa.

```js
import React, { useState } from "react";
import { useDispatch } from "react-redux";

import { addTask } from "../../store/actions";

function Home() {
  const dispatch = useDispatch();

  const handleAddTask = () => {
    dispatch(addTask('nova tarefa aqui'));
  };

  return (
    //...
  )
```

Para podermos adicionar uma nova tarefa, utilizaremos de um estado derivado do Hook `useState` do próprio **React** para capturar o valor de uma _tag_ `input` e executarmos o `handleAddTask` a partir do clique de uma _tag_ `button`.

```js
import React, { useState } from "react";
import { useDispatch } from "react-redux";

import { addTask } from "../../store/actions";

function Home() {
  const [newTask, setNewTask] = useState("");
  const dispatch = useDispatch();

  const handleAddTask = (e) => {
    /*
      Verificação para não adicionar tarefas vazias
    */
    if (newTask !== "") {
      dispatch(addTask(newTask));
    }

    /*
      Limpa o input assim que termina de adicionar a nova tarefa
    */
    setNewTask("");

    /* 
      Essa linha evitará que a página seja atualizada
      ao clicar no botão
    */
    e.preventDefault();
  };

  return (
    <main>
      <form action="">
        <input
          type="text"
          name="task"
          value={newTask}
          placeholder="Qual a próxima tarefa?"
          onChange={(e) => setNewTask(e.target.value)}
        />

        <button onClick={(e) => handleAddTask(e)}>Adicionar</button>
      </form>
    </main>
  );
}

export default Home;

```

A partir de agora, já é possível adicionarmos novas tarefas à Store apenas com o `input` e o `button`. Com o `dispatch` finalizado, precisaremos obter o _array_ de tarefas da store e renderizá-los em tela para disponibilizar a lista para o usuário. Aqui é onde entrará o Hook do `useSelector` também da biblioteca React-Redux.

```js
import { useSelector, useDispatch } from "react-redux";

/*
  ...
*/

function Home() {
  /*
    Com o Hook declarado e tendo acesso à Store, basta
    definirmos quais elementos queremos obter. Nesse caso,
    queremos o elemento **taskList** que declaramos dentro do 
    Reducer **tasks**, e podemos obtê-lo da seguinte forma:
  */
  const { taskList } = useSelector((state) => state.tasks);
  /*
    ...
  */

  return (
    /*
      ...
    */
  );
}

export default Home;

```

Estamos prontos para utilizarmos o _array_ de tarefas como quisermos. Para nossa aplicação, será renderizada uma simples lista com as tags `ul` e `li`.

```js
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";

import { addTask } from "../../store/actions";

function Home() {
  const { taskList } = useSelector((state) => state.tasks);
  const [newTask, setNewTask] = useState("");
  const dispatch = useDispatch();

  const handleAddTask = (e) => {
    dispatch(addTask(newTask));

    e.preventDefault();
  };

  return (
    <main>
      <form action="">
        <label>Qual a próxima tarefa?</label>
        <input
          type="text"
          name="task"
          value={newTask}
          placeholder="Qual a próxima tarefa?"
          onChange={(e) => setNewTask(e.target.value)}
        />

        <button onClick={(e) => handleAddTask(e)}>Adicionar</button>
      </form>

      /*
        Para uma melhor UI, adicionaremos uma contagem de quantas
        tarefas temos adicionadas até o momento.
      */
      <span>Minhas tarefas - {taskList.length}</span>

      /*
        Verificação para só renderizar a lista de o taskList não
        estiver vazio.
      */
      {taskList.length > 0 && (
        <ul>
          {taskList.map((task) => (
            <li>{task}</li>
          ))}
        </ul>
      )}
    </main>
  );
}

export default Home;
```

## Debug

Para que tenhamos uma melhor visão de como os estados estão se comportando durante a execução da aplicação, existem ferramentas de _debug_ que podemos utilizar para facilitar essa **visualização**. A recomendação atual é a de instalar uma extensão no navegador chamada **Redux Devtools**.

![Devtools na loja do Chrome](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zplbtytpg9qho2d0uzo8.png)
<figcaption>Extensão Redux Devtools</figcaption>

Ele será responsável por ouvir toda a sua aplicação e detalhar como está a **árvore de estados** dentro da Store, além de listar todas as **Actions que foram disparadas** e outras funcionalidades que não serão necessárias por agora.

![Lista de Actions disparadas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bufvhvj3tet0h0bwp9h1.png)
<figcaption>Lista de Actions disparadas com o resultado do estado atual na Store</figcaption>
  
## Resultado


![Aplicação com lista de tarefas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7jahgb8cxog541fpfoh.png)
<figcaption>Aplicação pronta</figcaption>
  
Para o resultado final do projeto, a construção do _layout_ com CSS foi omitida de forma que nos preocupamos apenas com o funcionamento do Redux. É possível acessar o [projeto no Github](https://github.com/eng-wilson/redux-concepts) para ver o código-fonte da estilização utilizada por mim, mas sinta-se livre para estilizar da sua forma.

## Conclusão

Com esse projeto, foi possível aprender quando usar Redux e qual a função dele dentro de uma aplicação. Passamos por todos os conceitos principais e construímos a base para tópicos mais complexos, como o Redux-Thunk, que será tema do próximo artigo. 
  
Para reforçar o conteúdo, recomendo adicionar um **desafio** para criar uma Action que irá **remover uma tarefa do _array_**.
  
Me siga para acompanhar o lançamento dos novos conteúdos, fique à vontade para enviar qualquer dúvida ou feedback e lembre-se de curtir e compartilhar se gostou do artigo e lhe foi útil.
  
Nos vemos logo mais.

[LinkedIn](https://www.linkedin.com/in/dev-wilson/)
[Github](https://github.com/eng-wilson)
[Twitter](https://twitter.com/wilson__io/)