Entendendo o estado no React

Dimas Cyriaco
7 min readJun 7, 2019

--

Eu sempre me surpreendo quando escuto alguém falar que React é complexo, ou aprender React é difícil.

O React em si é uma biblioteca bem simples. Acredito que a dificuldade está mais em como criar aplicações complexas usando o React.

A simplicidade do React é uma faca de dois gumes. Ao mesmo tempo que a funcionalidade do React em si é fácil de entender, e usar o React em projetos pequenos seja super simples, quando você passa a tentar fazer algo maior, é necessário ir além do React e buscar ferramentas no ecossistema para te ajudar com aplicações mais complexas.

E, provavelmente, a parte mais complexa de se criar uma aplicação grande usando o React, e que mais causa dúvida em quem está começando, é o ‘estado’.

Existem várias formas de tratar estado no React: o ‘state’ do componente propriamente dito, a API de Contexto, Redux/similares, ou até mesmo o LocalStorage. Cada uma dessas metodologias tem seus prós, seus contras e seus focos.

Nesse post eu vou tentar explicar qual tipo de estado é recomendável usar em cada contexto.

O que é ‘estado’

Em termos simples, ‘estado’ é todo dado que varia com o tempo. Essa mudança de valor pode, ou não, ser fruto da interação do usuário com a aplicação. Esse dado pode ir do óbvio, como o endereço de email que o usuário digita em um formulário, até o resultado de uma requisição `http` que tem que ser mostrada na tela.

Nesse post eu vou usar o termo ‘estado’ de uma forma bem abrangente, para designar tanto o estado interno de um componente como o ‘estado global’ de uma aplicação React, que são valores globais que devem ser definidos uma vez e utilizados em várias partes do código. Esse ‘estado global’ pode ser dinâmico, como o ‘carrinho’ em um site de e-commerce, ou estático/”não tão dinâmico”, como um tema de CSS.

State vs Props

É importante ter um bom entendimento sobre a diferença entre esses dois conceitos: o ‘state’ é utilizado para valores dinâmicos e props são utilizadas para passar valores entre componentes.

Esses dois conceitos não são mutuamente excludentes, muito pelo contrario. Props e state são usados em conjunto, mas, para reforçar esse entendimento, vamos ver mais alguns detalhes sobre os dois.

Estado do Component

O estado (state) de um componente React tem uma função muito simples e específica. Ele é uma propriedade do componente onde colocamos dados que, quando mudados, devem causar uma nova renderização. Simples assim. Se deve causar mudança fica no estado, se não deve, não fica.

Exemplo:

Usando hooks:

Nesses exemplos o counter vai ser atualizado toda vez que o usuário clicar no botão, causando uma nova renderização, que atualiza o valor na tela.

Mas agora vem a pegadinha: nem toda informação visual que muda deve ficar no estado.

O estado deve conter o mínimo necessário para conseguirmos derivar o estado visual do componente.

O tamanho do state dos componentes React afeta diretamente a performance (veja a documentação sobre reconciliação). Por isso é importante minimizar o tamanho do state.

Uma forma de fazer isso é ter atenção para estados que podem ser derivados de outros estados.

Exemplo:

Nesse exemplo a gente poderia derivar o hasError a partir da presença do errorMessage:

Assim a gente mantém a mesma funcionalidade mas usando um estado só.

Props

Esses são os valores que um componente recebe do ‘componente pai’. São como argumentos de uma função.

Nesse exemplo o componente Child recebe a prop name de Parent.

É muito comum existirem valores que são statede um componente, e precisam ser acessados em componentes muitos níveis abaixo.

Exemplo: imagine que o componente A renderize o componente B, que por sua vez, renderiza o componente C. E se em C você precisar acessar um valor que está no state de A?

A forma mais direta de tratar esses casos é simplesmente passar o valor como prop para B e de B passar para C.

Porém, isso pode adicionar complexidade e deixar o código difícil de entender. Como via de regra, é bom tentar evitar ‘passar props adiantes’ dessa forma. Imagine essa possível implementação de B:

Nesse caso Bnão tem relação nenhuma com myProperty. Ele só está recebendo essa prop porque ele precisa passar ela para C. Isso viola o [Single Responsability Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle), e é bom ser evitado.

Para evitar essa passagem de props, é muito comum existir um ‘estado global’ na aplicação que possa ser acessado diretamente por qualquer componente.

Context API

A forma mais ‘idiomática’ de se implementar esse ‘estado global’ é com a API de Contexto do React.

Essa API permite setarmos valores que podem ser acessados de qualquer parte da aplicação. Exemplo:

Dessa forma o valor de familyName pode ser acessado de qualquer lugar da aplicação sem ser necessário passá-lo como prop.

Porém a API de Contexto tem suas desvantagens. Ela cria uma dependência entre o Componente usando o contexto e o contexto em sí. O componente Person acima, não pode ser usado fora de um FamilyName.Provider. Isso pode ser um problema dependendo da aplicação.

A API de Contexto também deixa o componente mais difícil de testar, pois é necessário ‘mockar’ o Contexto para renderizar o componente.

A documentação do React tem [boas dicas](https://reactjs.org/docs/context.html#before-you-use-context) de como evitar usar a API de Contexto em certos casos.

Context.Consumer vs contextType

Nas situações onde a API de contexto é uma boa solução é necessário decidir entre usar o componente Context.Consumer e a propriedade estática contextType.

Exemplo com contextType:

A diferença básica entre essas duas estratégia é que o contextType nos dá acesso ao context nos métodos do life-cicle do React. Então caso você precise acessar o contexto no componentDidMount ou afins, o contextType é sua única alternativa.

Por outro lado, com o contextType só é possível usar um contexto em seu componente. Se você precisar usar mais de um contexto, a única alternativa é Context.Consumer.

Alterando o valor do Contexto

Não é comum, mas é possível alterar o valor do contexto dentro do Consumer.

É possível passar funções para o construtor do Contexto, e com isso mudar seu valor dentro de em Consumer.

Exemplo:

Para valores simples essa solução pode ser satisfatória, mas em casos mais complexos a gente começa a entrar no caso de uso do Redux e afins.

Estado Global com Flux/Redux e derivados

O Flux foi introduzido pelo Facebook junto com o React como a solução para tratamento de estado global.

Flux é apenas um padrão, e várias bibliotecas implementam esse padrão, ou um padrão parecido, sendo as mais conhecidas o Redux e o Mobx. Essas bibliotecas têm suas distinções e peculiaridades, mas no fundo a estratégia é muito parecida.

A ideia central é de que os dados fluem de forma unidirecional, ou seja, em uma só direção. O store é o local central onde os dados estão armazenados. Os componentes leem do store (normalmente na forma de uma subscription), e toda modificação deve ser feita através de actions. Toda mudança no store resulta na ‘re-renderização’ dos componentes que estão lendo dele.

Fonte: https://dimagimburg.com/reflux-sweeper-react-reflux-and-immutablejs-explained-by-example/

Um exemplo usando Mobx (um exemplo com Redux seria muito extenso):

Nesse exemplo o estado está concentrado na classe CounterStore. Qualquer componente que tenha acesso ao objeto store pode ler e ‘escrever’ nessa store. No exemplo, App faz um subscribe na store através do [decorator](https://github.com/tc39/proposal-decorators) observer. Com isso, toda vez que o counter mudar, o App vai ser atualizado.

A única forma de atualizar o counter (ou ‘escrever’ na store) é através da action increment que está sendo chamada no clique do botão.

Essa é a solução mais flexível, mas também é a solução que adiciona mais complexidade no sistema. É natural as aplicações crescerem e necessitarem de um estado global mais robusto, mas é bom ter atenção para não adicionar uma solução complexa quando uma das soluções mais simples pode solucionar o problema de forma satisfatória (ou seja, cuidado com o over-engeneering).

Bônus: LocalStorage

LocalStorage é um mecanismo de armazenamento não volátil disponibilizado pelo navegador.

Ser ‘não volátil’ significa que os dados não são perdidos quando o navegador é fechado. Ao re-abrir o navegador, os dados ainda estão lá. Isso é muito útil nas ocasiões onde você quer, ou precisa, manter informações entre seções.

Mas o LocalStorage tem alguns problemas.

Os dados são armazenados como ‘strings’. Para armazenar um objeto é necessário ‘stringficá-lo’ na escrita e re-parseá-lo na leitura. Dependendo da frequência de leitura e escrita isso pode impactar na performance da aplicação.

Outro ponto muito importante é que o LocalStorage é aberto. O usuário pode visualizar o conteúdo armazenado pelo ‘devtools’ do navegador. Inclusive é bom salientar que qualquer usuário com acesso a máquina pode visualizar o LocalStorage, mesmo que ele não esteja logado na sua aplicação. É responsabilidade do desenvolvedor cuidar da privacidade dos usuários. Por isso, é recomendável não armazenar nenhum dado pessoal do usuário no LocalStorage.

Mesmo que exista no codebase uma ‘limpeza’ do LocalStorage no logout do usuário, lembre que em caso de fechamento de aba ou um crash do navegador, seu código não será executado. Então, como não temos como assegurar a limpeza dos dados, é prudente assumir que qualquer dado colocado no LocalStorage pode ser visualizado por qualquer usuário daquele navegador. Então só devemos colocar informações no LocalStorage que possam ser visualizados por qualquer pessoa.

Nos casos em que é valido usar o LocalStorage, uma regra útil, é interpolar um identificador do usuário logado na key do storage. Por exemplo:

Dessa forma, não vai haver colisão com o valor setado por outro usuário que logar no mesmo navegador.

Quando usar

O ideal é usar o LocalStorage para informações que não serão escritas ou lidas com frequência e que precisem ser mantidas entre seções.

Lembrando sempre de levarmos em consideração o caso de computadores compartilhados.

Conclusão

Como a gente pode ver nos exemplos acima, cada tipo de gerenciamento de estado tem seus prós e seus contras.

Acredito que a melhor forma de decidir quando usar o que, é pensar no contexto e tentar achar a solução mais simples que resolva nosso problema.

--

--