Recoil - biblioteca de gerenciamento de estados no React

ReactJS 30 de Jul de 2020

Vamos falar de React com um assunto interessante: Gerenciamento de estados e a nova lib chamada Recoil.

O que é?

Recoil é uma biblioteca experimental de gerenciamento de estado para aplicações feitas em React. A biblioteca fornece vários recursos que são difíceis de implementar usando apenas o React — Está disponível desde maio de 2020.

No package.json da lib observamos que ela é feita com React, Immutable e TypeScript, inclusive a última versão já suporta TypeScript.

Quem criou?

Os desenvolvedores do Facebook criaram essa lib, eles usam internamente nos seus projetos há algum tempo. Disponibilizaram o código no GitHub, porém ela está em fase de experimentação. Não é recomendável colocar no seu projeto em produção.

Por que foi criada?

O React por si só já tem gerenciamento de estados com o famoso hook useState e também Context API. Porém ambas tem suas limitações quando a aplicação ganha escala e precisamos compartilhar estados entre componentes de forma transversal.

Quais limitações?

  • No React passamos estados vias props e podemos ter um PropsHell mesmo usando hooks.
  • O contexto pode armazenar apenas um único valor - não um conjunto indefinido de valores, cada um com seus próprios consumidores. E com isso podemos ter um ContextAPIHell.

👍 Vantagens

  • Foi criada pelos engenheiros de softwares do Facebook, sabemos que seus produtos são altamente escaláveis e com isso seus códigos precisam ser também.
  • React é uma lib incrível o que nos dá confiança que Recoil possa ser incrível também.
  • Mais fácil de configurar do que outras libs de gerenciamento de estados
  • Cada estado é representado por um átomo e eles podem ficar em arquivos separados dos componentes e serem utilizados em outros componentes de maneira bem simples.
  • Recoil tem um bundle menor que outras libs que gerenciam estados como Redux e Mobx.
  • Se você sabe usar useState você sabe usar atom e outras funções do Recoil. Recoil is React-style — Bastante semelhante ao jeito React de fazer as coisas.

👎 Desvantagem

  • Por estar em fase de experimentação, infelizmente ainda não é recomendável colocar em produção.

Onde aplicar?

Quando precisar de flexibilidade para compartilhar estado na aplicação. Utilizar dados derivados de algum estado com poder computacional das mudanças no estado que são refletidas nos componentes de maneira eficiente. E também se precisar fazer debugging e logging, ou seja observar as mudanças de estados de maneira eficiente.

Basicamente quando encontrar limitações com o gerenciamento de estado com React ou Redux, segundo Dave McCabe, em sua apresentação sobre Recoil.

Representação gráfica do Recoil

✅ Principais Conceitos e funções básicas

⚛️ Atoms

Os átomos são uma unidade do estado, ele possui uma chave única e um valor default que pode armazenar todos os tipos disponíveis no JavaScript. Podemos usar o átomos (atom) no lugar dos componentes locais.

Átomos são criados com:

const usernameState = atom({
  key: 'usernameState',
  default: ''
});

É o equivalente ao useState:

const [ username, setUsername ] = useState('')

⚛️ useRecoilState

Esse hook serve para inscrever o componente no átomo, e permite acessar e alterar o valor do mesmo. Para acessar e manipular o átomo usamos essa função.

function inputUserName() {
  const [username, setUsername] = useRecoilState(usernameState);
  return (
  Username: <input placeholder="Digite o username" value={username} onChange={(e) => setUsername(e.target.value)} />
  );
}

⚛️ useRecoilValue

Usamos esse hook quando pretendemos apenas acessar o átomo. Este hook inscreve o componente no átomo. Se quisermos apenas acessar o valor do username, podemos usar useRecoilValue.

function showUsername() {
  const username = useRecoilValue(usernameState);
  return (
    Username: <span>{username}</span>
  );
}

⚛️ Seletores

Seletores são funções puras que acessam átomos e permitem modificar o estado sem ter que recriar o estado:

const userNameInUpperCaseState = selector({
  key: 'userNameInUpperCaseState',
  get: ({get}) => {
    const username = get(usernameState);
    const usernameUpperCase = username.toUpperCase();

    return usernameUpperCase;
  },
});

No exemplo acima pegamos o estado de usernameState que usamos a função do JavaScript para deixar o texto com letras maísculas.

É utilizado a função get que tem acesso a todos os átomos, com o átomo em mãos podemos atualizá-lo e retornar o novo valor.

Os seletores são usados para calcular dados derivados baseados no atom (estado) existente.

Exemplo, o queijo é derivado do leite, então teríamos:

const milk = atom({
  key: 'milkFarmState',
  default: 'Leite'
});

Dados derivados:

const cheese = selector({
  key: 'cheeseFarmState',
  get: ({get}) => {
    const milk = get(milk);
    const cheese = makeACheeseWith(milk);
    return cheese;
  },
});

Observe que com o estado de milk, derivamos o mesmo para cheese. Não precisamos criar outro estado milk para poder criar um cheese, aproveitamos o estado existente e criamos um outro somente leitura que é o cheese, observe que não podemos alterá-lo.

Os seletores podem ser acessados usando o hook useRecoilValue, eles estão apenas acessíveis e não podem ser alterados.

function showUsername() {
  const upperUsername = useRecoilValue(userNameInUpperCaseState);
  return (
    USERNAME: <span>{upperUsername}</span>
  );
}

⚛️ RecoilRoot

A proposta do Recoil é ter um único componente RecoilRoot na raiz da aplicação e de baixo dele ter os átomos – que são os estados manipulados por funções pura, chamadas de seletores – nos componentes. O estado é utilizado pelo componente, mas não pertence ao componente. Isso o torna bastante escalável para que outros utilizem e modifiquem o estado conforme sua necessidade ou até mesmo sejam transformados.

Os componentes que usam os estados (atoms) do Recoil precisam estar abaixo de um componente RecoilRoot, semelhante ao Redux – que precisa de um provider e ao Context API – porém o RecoilRoot não recebe props.

import React from 'react';
import { RecoilRoot } from 'recoil';

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

👩‍💻 Hands-on – Bora codar!

Vamos criar um React App bem simples para demonstrar na prática os conceitos e funcionamento do Recoil.

Criaremos os atoms em uma pasta exclusiva, separados dos componentes para ficar claro que eles são independentes de qualquer componente.

Iremos importar o atom e utilizar com useRecoilValue para acessar o valor ou useRecoilState para acessar e modificar o valor, e selector para transformar o estado em um estado derivado.

🔓 Pré-requisitos: ter o Node instalado e conhecimento em React.

Crie o react-app:

npx create-react-app try-recoil

Abra a pasta, adicione o recoil e abra o projeto no VSCode:

cd try-recoil && yarn add recoil && code .

Inicie o app

yarn start

Vamos apagar os arquivos logo.svg, serviceWorker.JS, App.css, manifest.json, logo512.png, logo192.png, e remover nos arquivos as respectivas referências.

Arquivo App.js vai ficar só assim:

import React from "react";

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

export default App;

No index.js vamos começar a envolvendo o componente RecoilRoot no componente App, dessa forma tudo que for relacionado ao Recoil ficará disponível nesse contexto.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { RecoilRoot } from "recoil";

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById("root")
);

Agora vamos criar um pasta atoms dentro da pasta src que irá conter nosso primeiro átomo no arquivo index.js que iremos criar na nova pasta.

src/atoms/index.js

import { atom } from "recoil";

export const usernameState = atom({
  key: "usernameState",
  default: "Thiago",
});

Importei a função atom que cria o estado, utilizei ela na criação de uma constante que é exportada, a função recebeu um objeto com duas propriedades:

  • key: uma string que é a chave única.
  • default: pode receber qualquer valor do JavaScript, nesse caso está recebendo já um valor padrão que por enquanto é 'Thiago'.

No arquivo App.js, iremos utilizar o átomo:

import React from "react";
import { useRecoilValue } from "recoil";
import { usernameState } from "./atoms";

function App() {
  const username = useRecoilValue(usernameState);

  return <h1>{username}</h1>;
}

export default App;

Simplesmente importei a constante usernameState da pasta atoms e a função useRecoilValue do recoil que irá apenas acessar o valor e atribuir em uma nova constante username e que será usada no <h1>{username}</h1>, o que irá fazer com que o nome "Thiago" seja impresso na tela da aplicação.

Inicie a aplicação:

yarn start
aplicação executando com Recoil configurado

Para alterar o estado iremos usar o useRecoilState.

import React from "react";
import { useRecoilValue, useRecoilState } from "recoil";
import { usernameState } from "./atoms";

function App() {
  const username = useRecoilValue(usernameState);
  const [userEdit, setUserEdit] = useRecoilState(usernameState);

  return (
    <>
      <h1>{username}</h1>
      <br />
      Username:
      <input
        placeholder="Digite o username"
        value={userEdit}
        onChange={(e) => setUserEdit(e.target.value)}
      />
    </>
  );
}

export default App;

Importamos o useRecoilState,  e criamos um novo estado userEdit para armazenar o usernameState e alterar o seu valor.

Criamos um input que no value recebe o userEdit e no onChange cada vez que digitamos um nome o userEdit é alterado com a função setUserEdit, essa alteração reflete tanto no value do userEdit quanto no username que está no <h1> por que tanto useRecoilValue e useRecoilState estão referenciando o mesmo átomo.

conforme digito no input o h1 é alterado

Agora vamos ver o selector em ação, criei uma pasta selector dentro da pasta atoms e um arquivo index.js.

src/atoms/selector/index.js:

import { selector } from "recoil";

import { usernameState } from "../../atoms";

export const upperUsername = selector({
  key: "upperUsernameState",
  get: ({ get }) => {
    const username = get(usernameState);
    const usernameUpper = username.toUpperCase();
    return usernameUpper;
  },
});

Importe do recoil o selector

Importe o átomo que queremos transformar (usernameState).

Crie o estado upperUsername que será derivado do usernameState.

Selector é uma função que recebe um objeto como parâmetro:

  • key: uma string que é a chave única em toda a aplicação.
  • get: recebe uma função e passa obrigatoriamente o get como parâmetros para a função pura, o get é o método responsável por acessar qualquer átomo, porém precisamos passar o átomo como referência para a função get.

Simplesmente usei a API do JavaScript para deixar username maiúsculo e retornei o resultado.

Para utilizar vamos no arquivo App.js:

import React from "react";
import { useRecoilValue, useRecoilState } from "recoil";
import { usernameState } from "./atoms";
import { upperUsername } from "./atoms/selector";

function App() {
  const username = useRecoilValue(usernameState);
  const [userEdit, setUserEdit] = useRecoilState(usernameState);

  const usernameUpper = useRecoilValue(upperUsername);

  return (
    <>
      <h1>{username}</h1>
      <br />
      Username:
      <input
        placeholder="Digite o username"
        value={userEdit}
        onChange={(e) => setUserEdit(e.target.value)}
      />
      <br />
      <br />
      <span>{usernameUpper}</span>
    </>
  );
}

export default App;

Importamos o nosso estado derivado upperUsername que criamos na pasta selectors.

Acessamos o seu valor usando o useRecoilValue(upperUsername)

Por fim colocamos dentro do <span> para exibir usernameUpper, que irá trazer o estado com texto maísculo.

Conforme digito Rocketseat o span mostra o texto em maísculo

Sempre iremos usar o selector para transformar os dados, por exemplo em uma lista de todo, se quisermos listar apenas o todos que não foram feitos, basta usar um selector e filtrar o array de todos e retornar o resultado, uma vez que os todos estarão armazenados em um atom.

Pronto! Terminamos o nosso exemplo.

Código fonte: https://github.com/tgmarinho/try-recoil

Conclusão

Com a mudança do React trazendo os Hooks, até mesmo o Redux ficou bem mais fácil de utilizar. Mas o que gostei foi da facilidade de começar organizar o projeto com Recoil, os conceitos são bem simples, assusta um pouquinho com o nome Atoms, mas é só saber que um atom é um estado, useRecoilState é um useState, e por aí vai. Achei mais fácil aprender gerenciamento de estados com Recoil do que Redux e Context API, por ser React-style.

A lib é nova, porém tem um grande potencial, vamos acompanhar os próximos capítulos dessa biblioteca.  

Você pode acompanhar algumas notícias da lib por aqui.

🔗 Plus

  • Quer ver um pouco de Recoil na prática, olha esse repositório que o Diego está criando o RocketRedis.
  • O Guilherme Rodz fez um Code Challenge sobre Recoil bem legal assim que a lib foi lançada. Rodz constrói um app que busca os usuários do Github e usa o selector para pegar os seguidores de cada usuário e mostrar a quantidade de followers que cada um tem, confira aí.

Espero que tenha curtido! 💜

O aprendizado é contínuo e sempre haverá um próximo nível! 🚀

Marcadores

Thiago Marinho

Dev Apaixonado por Tecnologia & Educação! Evolua rápido como a tecnologia, aprendizado é contínuo e sempre haverá um próximo nível. Boost Yourself! 🚀