React / Redux básico
Tags: react, redux, thunk, middlewares
Published: 2020-12-16 22:35:52

Chega um momento no estudo do React em que devemos enfrentar o Redux. Quando eu lidei com isso da primeira vez foi difícil e eu fiquei um tempo confuso, mas com o tempo a sua estrutura foi ficando mais clara, então eu decidi, como material de estudo, criar um esquema. Aos poucos vou melhorando esse esquema para torná-lo mais prático, e acredito que ele possa ajudar outros estudantes do assunto, assim resolvi publicá-lo aqui, seguido de explicações:

<iframe style="border: 1px solid rgba(0, 0, 0, 0.1);" width="800" height="450" src="https://www.figma.com/embed?embed_host=share&url=https%3A%2F%2Fwww.figma.com%2Ffile%2FUWtybjLYUS9pRVJvDIWkgP%2FRedux%3Fnode-id%3D0%253A1" allowfullscreen></iframe>

[link para imagem mais visível](https://www.figma.com/file/UWtybjLYUS9pRVJvDIWkgP/Redux?node-id=0%3A1)

Para começar, é preciso importar as bibliotecas do Redux:

- [redux](https://redux.js.org/)

- [react-redux](https://react-redux.js.org/)

- [redux-thunk](https://github.com/reduxjs/redux-thunk)

- bônus: [redux-devtools-extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)

É só mandar um

```jsx

yarn add redux react-redux redux-thunk redux-devtools-extension

```

1.Estrutura de pastas

Uma boa estrutura inicial de pastas para o Redux seria a seguinte:

```bash

    redux
        ├── types
        │   └── index.js
        ├── actions
        │   └── index.js
        ├── thunk
        │   └── index.js
        ├── reducers
        │   └── index.js
        └── store
                └── index.js

```

Essa é apenas uma proposta, e há quem não utilize a pasta `store`, preferindo utilizar o arquivo `index.js` direto da pasta redux, por exemplo, ou use os **thunks** junto das **actions**...

Faça a sua escolha! Busque a estrutura que lhe parecer mais lógica.

2.Responsabilidades

- Types

Definição de variáveis. Usamos variáveis no lugar de strings para evitar erros de digitação, pois essas variáveis serão utilizadas diversas vezes em nossa aplicação.

```jsx
// types/index.js -- ou types/products.js

export const SETLIST = "SET LIST";
export const RESET = "RESET";
```

- Actions

Funções criadoras de ações. Aqui são criados objetos que irão passar para o store o valor a ser alterado em nosso **estado**. Actions são funções puras, e tudo o que elas fazem é montar um objeto que será lido no **reducer**.

```jsx
// actions/index.js -- ou actions/products.js

import { SETLIST, RESET } from '../types'

export const setList = ( newList ) => ({
    type = SETLIST,
    newList
})

export const reset = () => ({
    type = RESET,
})
```

- Thunk

Eu gosto de separar os **thunks** das **actions**, embora visualmente elas pareçam a mesma coisa. Aqui fica a lógica do que deve ser feito como _efeito colateral_ antes de passar os dados para a **action**. _Efeito colateral_ é o que altera dados fora do escopo da nossa função. Pode ser, por exemplo, a persistência de dados em um banco de dados ou em um arquivo csv.

No exemplo abaixo, a função _addProduct_ recebe um produto por parâmetro, envia esse produto para o servidor, recebe como resposta a lista atualizada de produtos e envia essa lista atualizada para o **reducer**, utilizando a action _setList_ (observe a diferença de responsabilidades entre _actions_ e _thunks_)

```jsx
// thunk/index.js -- ou thunk/products.js

import setList from '../actions'

export const addProduct = ( product ) => ( dispatch ) => {
    const url = ...
    const headers = { ... }
    axios.post(url, { product: product }, headers)
    .then(( res ) => {
        dispatch( setList( res.data.list ))
    })
    .catch(error => ...)
}
```

NB: o **axios** trata os dados pelo formato correspondente; se for utilizar o _fetch_ será preciso converter os dados para string e para json nas ocasiões apropriadas.

- Reducers

Os **reducers** recebem os novos dados e atualizam o estado de maneira correspondente. É aqui que o estado é iniciado e fica armazenado. Normalmente, é “setado” um estado inicial vazio, que pode ser utilizado para posteriormente se limpar o estado.

```jsx

// reducers/index.js -- ou reducers/products.js

import { SETLIST, RESET } from '../types'

const defaultList = []

const productList = (state = defaultState, action) => {
    const { type, newList = [] } = action

    switch type {

        case SETLIST:
            return newList

        case RESET:
            return defaultList

        default:
            return state
    }
}

export default productList
```

Aqui é tomada uma ação de acordo com o valor recebido. No nosso exemplo, um novo produto é enviado para o **thunk**, que atualiza o _back-end_ e retorna a lista atualizada. O **thunk** então despacha uma **action** com a nova lista, que é recebida no **reducer** , e é utilizada para atualizar o estado.

O novo estado vai assumir o valor retornado pelo _switch_

- Store

A **store** recebe os valores do **reducer** e registra o estado de forma a torná-lo disponível em todos os lugares da aplicação.

```jsx
// store/index.js

import { createStore, applyMiddleware } from "redux";

// opcional: esse item permite o debug do reducer no devtools do Chrome.
import { composeWithDevTools } from "redux-devtools-extension";

import thunk from "redux-thunk";

// o reducer **productList** exportado por default recebe outro nome no import:
import reducers from "../reducers";

const store = createStore(
  reducers,
  composeWithDevTools(applyMiddleware(thunk))
);

export default store;
```

3.Estrutura mais complexa

Nosso exemplo acima é bem simples, mas aplicações volumosas podem ter várias **actions**, **thunks** e **reducers**, por exemplo para usuários (cadastro, login, alteração de dados...) e produtos no carrinho (adicionar, remover, limpar...) ou outros tipos de dados (mensagens entre usuários, chat com robô para tirar dúvidas, etc). Nesse caso, cada arquivo terá seus dados correspondentes, por exemplo:

```bash

    ├── reducers
    │  ├── index.js
    │  ├── users.js
    │  ├── products.js
    │  └── messages.js

```

Nesse caso, seu _index.js_ deve fazer a junção desses **reducers** (com as **actions** e **thunks** você não precisa se preocupar, pois eles são disponibilizados via **import**, então a estrutura de pastas não altera sua funcionalidade):

```jsx
// reducers/index.js

import { combineReducers } from "redux";

// aqui importamos todos os reducers:
import users from "./users";
import products from "./products";
import messages from "./messages";

const store = combineReducers({ products, users, messages });
export default store;
```

4.Pondo para funcionar

Para que a estrutura acima funcione, temos que disponibilizá-la globalmente em nossa aplicação. fazemos isso no arquivo _App.jsx_:

```jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";

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

O componente **Provider** recebe nossa **store** e a disponibiliza para tudo o que estiver contido dentro do componente **App** - isto é, toda nossa aplicação.

**N.B.**: Se sua aplicação tem uma estrutura simples, com apenas um **reducer**, você pode exportar seu único **reducer** e passá-lo para o **Provider** como valor de **store**, pulando o item 3 acima.

5.Usando e atualizando o estado no **redux**

Uma vez estruturado o estado no seu **redux**, agora você pode acessá-lo de qualquer lugar da sua aplicação, ou atualizá-lo, por meio dos **hooks**.

```jsx

import { useSelector, useDispatch } from 'react-redux'
import { addProduct } from '../redux/thunk'
import { reset } from '../redux/actions'

const myComponent = () => {
    // vc tem que instanciar o useDispatch dentro do seu componente:
    const dispatch = useDispatch()

    // atualizar os dados
    const newProduct = {id: 5, name: 'flipflops', size: 45, gift: true }
    dispatch(addProduct(newProduct))

    // recuperar dados do redux
    const myList = useSelector(state => state.productList)

...

return(
    ...
    <button onClick={() => dispatch(reset())}>Limpar carrinho</button>
)

```

Pronto! Nada de dados passando por **props** na lógica do nosso carrinho de compras.

O uso do **Redux** não nos priva de utilizarmos **useState** ou passar dados por **props**, mas agora esses recursos ficam circunscritos à lógica interna do componente. A parte pesada da manipulação do estado, com dados que precisam ser acessados de páginas e rotas distintas, é muito bem administrada pelo **Redux**.

Existem outros **hooks** e outras formas de fazer uma estrutura parecida com a descrita acima; a documentação do Redux pode ser consultada em [https://redux.js.org/](https://redux.js.org/), e a do Thunk em [https://www.npmjs.com/package/redux-thunk](https://www.npmjs.com/package/redux-thunk).

Para debugar o **redux**, eu inseri acima um código que permite a comunicação da sua aplicação com o **redux dev tools**, que funciona como uma extensão do navegador. Veja a documentação em [https://github.com/zalmoxisus/redux-devtools-extension](https://github.com/zalmoxisus/redux-devtools-extension)

Algumas considerações:

- A estrutura acima é apenas uma proposta. Pode não ser a mais prática, mas eu tentei apresentar a estrutura mais lógica e enxuta possível - há quem prefira usar **actions** e **thunks** juntos na pasta das **actions**... isso pode concentrar melhor os conteúdos, mas há uma diferença entre o que cada elemento faz, e isso também determina de que maneira devemos procurar por erros quando alguma coisa quebra no nosso código. Se uma ação despachada da aplicação altera o estado, mas não atualiza o banco de dados, onde você vai procurar o erro? No local onde é feito o **fetch**, ou seja, em um **thunk**. Se é o contrário, atualiza o servidor mas não o estado local... bom, aí o problema pode estar em qualquer lugar _a partir do retorno do_ **fetch**: no **thunk**, na **action** posterior ou no **reducer** que vai tratar essa **action**.

- Também é comum ver as **types** sendo exportadas das **actions**. Mas, se você parar para pensar, são coisas distintas, e é bonito ver cada coisa no lugar certo.

- A divisão das **actions**, **reducers** e **types** em pastas e arquivos separados se mostra mais eficaz quando um projeto grande é desenvolvido simultaneamente por várias pessoas. Cada uma delas, na sua _branch_, vai criar essas pastas, e na hora de fazer _merge_, dificilmente vai ter algum conflito, pois, no final, o que teremos serão arquivos separados. Pode haver conflito nos pontos de confluência, como no arquivo **reducers/index.js**, onde haverá o _import_ dos **reducers** e eles terão que ser passados para o **combineReducers**. Mas a manutenção disso é muito mais fácil (a princípio é apenas uma linha) do que deixar tudo em um só lugar, inclusive no caso dos arquivos na pasta **types**.

```bash
# branch 1 -- products
redux
├── reducers
│  ├── products.js
│  └── index.js
├── actions
│  ├── products.js
│  └── index.js
...etc

# branch 2 -- users
redux
├── reducers
│  ├── users.js
│  └── index.js
├── actions
│  ├── users.js
│  └── index.js
...etc

# master, aṕos merge
redux
├── reducers
│  ├── products.js
│  ├── users.js
│  └── index.js
├── actions
│  ├── products.js
│  ├── users.js
│  └── index.js
...etc

```

Na estrutura acima, o único conflito a ser tratado no _merge_ será a linha que contém o export no _index_ de cada pasta:

```jsx
// reducers/indexs.js -- brach 1 (products)
import productList from './products.js'
...
const reducers = combineReducers({ productList });

// reducers/indexs.js -- brach 2 (users)
import userList from './users.js'
...
const reducers = combineReducers({ userList });

// reducers/indexs.js -- master após merge
import userList from './products.js'
import userList from './users.js'
...
const reducers = combineReducers({ productList, userList });
/* ^ a linha acima é a única que pode dar conflito
no momento do merge, facinho de dar manutenção */
```