Substituindo o Redux pelo Context API & React Hooks

Atenção: esse código e artigo foram reescritos e publicados recentemente no meu site pessoal. Recomendo que leia a versão mais atualizada através desse link.

Há um tempo atrás o time do React lançou algumas novas features, incluindo Context API, que te permite compartilhar propriedades na sua aplicação, e React Hooks, que são algumas funções que dão muito mais flexibilidade e poder para seus stateless components.

E no meu último projeto do trabalho eu resolvi substituir o gerenciamento de estado do Redux pelo Context API & React Hooks. Pra ser sincero, gostei bastante do resultado e gostaria de compartilha-lo com vocês. Eu tentei deixar o mais parecido possível com o Redux, usando o padrão Ducks pra organizar os arquivos, com middleware, types, actions, reducers, etc.

No exemplo a seguir estarei criando um simples counter com autenticação. Usei o React Create App para iniciar o projeto. Caso queira, você pode ver todo código a seguir nesse repositório ou a demo final nesse link.

Vamos ao código.

Primeiro vamos criar a configuração da nossa Store.
src/store/config/config.js

O componente Connect para conectar e injetar a store e o dispatch na página.
src/store/config/connect.js

E envolver nossa aplicação pra nossa Store ficar disponível dentro dela.
src/index.js

Agora vamos criar os estados dentro da nossa Store. Primeiro, eu vou criar a configuração do nosso counter.
src/store/counter.js

Como mencionei, estou utilizando o padrão Ducks com todos os types, actions, reducer e state no mesmo arquivo. Precisamos apenas exportar o counterReducer como padrão e o counterStore para injetar no nosso Provider.

Agora vamos criar o componente Provider, ele será o mais importante e responsável por gerenciar nossos reducers e o estado da nossa aplicação.

Explicando o código a cima, na linha 6
Eu estou usando o Hook useReducer que recebe um switch e devolve um objeto.

Linha 11
A const combinedReducer
é um objeto que recebe nossos estados e um dispatch, que dispara o switch do useReducer.

Linha 19
Estou injetando os valores no context.

Ok, agora vamos conectar um componente na nossa Store e tentar receber esses dados.
src/container/counter.js

Explicando o código, eu estou importando a função Connect e envolvendo nosso componente pra receber as propriedades da nossa Store e o dispatch.

Também estou importando as actions DecrementCounter e IncrementCounter, que retornam um objeto com um type e é sempre chamada dentro do dispatch, que interage no switch e atualiza a nossa Store. Meio complicado mas é o padrão que o Redux usa.

Ótimo, agora já conseguimos interagir e atualizar nossa Store.

Autenticação

Agora vamos criar um estado na nossa Store que vai ficar responsável pela autenticação e guardar as informações do usuário na nossa plataforma.
src/store/auth.js

Agora precisamos adiciona-lo no nosso arquivo principal, o provider. Mas se você reparar, nosso dispatch está passando apenas por um reducer. Precisamos deixar ele dinâmico e passar por todos reducers que tivermos. Pra isso, vamos fazer a seguinte alteração.

Explicando,
Linha 4— Estou importando o authReducer, { authStore }.
Linha 12 — Atribui eles num useReducer.
Linha 14 — Criei a função triggerDispatchs que tem um array de reducers e interage entre todos eles.
Linha 24 — Adicionei o authState na nossa Store.

Agora vamos criar nosso componente responsável pelo Login.
src/container/login.js

Da mesma forma, estou importando a função Connect, que conecta na Store e chamando a action responsável pelo login dentro do dispatch.

Agora sempre que a função dispatch for chamada, ela vai interagir por todos nossos reducers, graças a nossa função triggerDispatchs, atualizando toda a Store se houver um match com o type que passarmos.

Pronto, nesse ponto já temos dois estados independentes na nossa Store e sempre que quisermos adicionar um outro estado na Store, precisamos fazer as seguintes alterações:

1 — Criar um reducer e o estado inicial.
2 — Importa-los no arquivo Provider.js.
3 — Adiciona-los em mais um useReducer.
4 — Atribui-los em triggerDispatchs e combinedReducer.

Adicionando Middlewares

Middlewares são funções que são chamadas no meio de algum processo. Podemos usa-lo para capturar um evento e fazer algo que queremos, como, por exemplo, guarda ou limpar as informações no local store, chamar outra action e etc. Pra isso vamos criar nosso arquivo de middleware.
src/store/config/middleware.js

Agora a única alteração que precisamos fazer é criar mais uma função chamada middlewareConstructor, que passa a action atual e o triggerDispatchs para nosso middleware, e atribui-la no nosso dispatch.

Pronto, agora sempre que o dispatch for chamado, ele vai passar pelo nosso middleware.

Adicionando MapStateToProps

Se você quiser escolher quais propriedades devem ser injetadas no seu componente, como o mapStateToProps, podemos mudar nosso arquivo connect pra seguinte forma.

Explicando,
Linha 4 — Uma função que apenas retorna as props por padrão.
Linha 12 — Caso a prop mapStateToProps exista, eu filtro a Store por ela, se não, eu apenas retorno as props do componente, usando a função da linha 4.

Agora não podemos esquecer de mudar nossos componentes que estão conectados na Store, passando a função mapStateToProps como o primeiro parâmetro e o componente em si como segundo.

Pronto, agora temos um app usando o Context API e useReducer para gerenciar seu estado, de forma nativa do React. Não precisamos mais da biblioteca Redux e temos o mesmo resultado.

Bem, quase o mesmo resultado…

Pois encontrei duas desvantagens. Não poder trabalhar com actions assíncronas, como o redux-thunk e também não temos a aba Redux do chrome para debugar o estado da nossa aplicação.

Melhorias

Uma melhoria que podemos fazer também é passar o estado inicial da Store na criação do nosso Context. Assim, nossos testes unitários não irão falhar por não encontrar essas propriedades nas props.

Código Fonte

Como citei em cima, todo código desse projeto está disponibilizado nesse repositório e a demo nesse link.

Qualquer sugestão, crítica ou comentário é bem vindo.

A Front-End developer who loves to create and share experiences.