React: Organizando seu useReducer
Tags: ptbr, webdev, react, javascript
Published: 2019-08-16 09:02:03

## Pequenos padrões criando grandes diferenças!

[Com a introdução do React Hooks](https://reactjs.org/docs/hooks-intro.html), a criação de estado local e global ficou um pouco mais simples (dependendo do ponto de vista né?) e toda a criação de estado está [propenso a ser puro/imutável, pois a referência do Hook muda a cada renderização](https://medium.com/@oieduardorabelo/react-hooks-n%C3%A3o-%C3%A9-m%C3%A1gica-apenas-arrays-ec04ab7776da).

As duas opções nativas do React são **useState** e **useReducer**.

Se você já vem andando por esse mato a algum tempo, pode ter ouvido "use o useState para casos simples e o useReducer para casos complexos" ou "ah mas o useState usa o useReducer por baixo do capô" e para finalizar "o useReducer é o Redux no React, prefiro useState" (🤷‍♂️🤷‍♂️🤷‍♂️).

Opiniões a parte, o **useState realmente faz uso do useReducer por baixo do capô**, você pode conferir [o trecho do código do reconciliador do React no GitHub](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L803:L834) (o link pode/deve mudar no futuro! 😆).

Eu gosto dos dois, mas hoje, **vamos falar do useReducer**.

## Começando com a documentação

Olhando a [documentação de referência do React Hooks](https://reactjs.org/docs/hooks-reference.html#usereducer), nos temos o seguinte exemplo com **useReducer**:

```jsx
let initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  let [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
```

Com estados pequenos como esse, essa estrutura até que funciona por um bom tempo.

Qual seria o próximo passo então?

## Extraindo as ações

Assim como o Redux, a idéia de **action creators** é bem válida com **useReducer**. Como eu gosto de ir passo a passo, normalmente começo isolando as ações e criando um objeto com chave (nome da ação) e valor (a função que retorna um novo estado). 

Essa função recebe como argumentos o estado atual/anterior e a ação em si. Sempre retornando um novo estado.

Removemos o `switch` em favor de um `if..else`, deixando a leitura mais simples. E, nesse caso minha preferência pessoal, ao invés de jogar um erro, eu prefiro logar quais ações não tem um redutor correspondente. Ficando mais simples a iteração entre aplicação no navegador e código.

Chegando ao seguinte código:

```jsx
let initialState = {count: 0};
let reducerActions = {
    increment: (state, action) => {
      return {count: state.count + 1};
    }
    decrement: (state, action) => {
      return {count: state.count - 1};
    }
};

function reducer(state, action) {
    let fn = reducerActions[action.type];

    if (fn) {
      return fn(state, action);
    }

    console.log('[WARNING] Action without reducer:', action);
    return state;
}

function Counter() {
  let [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
```

Ficou um pouco melhor. Porém, essas funções no `reducerActions` precisam retornar um novo estado e, manualmente atualizar seus valores, é propenso a erros! Acredito que você se lembra de cenários como `{ ...state, chave: { ...state.chave } }`, isso já me trouxe muitos pesadelos. 😣

Então, como podemos melhorar essa parte?

# Estados imutáveis com operações mutáveis

Uma biblioteca que eu adoro e que também ganhou os prêmios **‌Breakthrough of the year** no [React Open Source Awards](https://osawards.com/react/) e **‌Most impactful contribution** no [JavaScript Open Source Award](https://osawards.com/javascript/) em 2019, é a biblioteca [immer](https://github.com/immerjs/immer).

Com ela, podemos garantir que toda a mudança dentro das nossas funções redutoras irão retornar um novo estado, sem a complicação de `...` a cada `{ ...{ ...{} } }` que você criar.

Antes de passar o estado como argumento para nossas funções redutoras, invocamos o `immer` e retornamos o estado temporário criado para as funções redutoras.

Ficando com o seguinte código:

```jsx
import immer from 'immer';

let initialState = {count: 0};
let reducerActions = {
    increment: (state, action) => {
      state.count += 1;
    }
    decrement: (state, action) => {
      state.count -= 1;
    }
};

function reducer(state, action) {
    let fn = reducerActions[action.type];

    if (fn) {
      return immer(state, draftState => fn(draftState, action));
    }

    console.log('[WARNING] Action without reducer:', action);
    return state;
}

function Counter() {
  let [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
```

Como você pode perceber, agora podemos utilizar operações mutáveis dentro do nosso redutor, de modo totalmente seguro. Garantindo que um novo estado imutável/puro seja retornado.

Tudo isso é bem legal nesse exemplo da documentação, mas, como ficaria isso em algo mais dinâmico, como uma chamada de API?

# Chamadas de API e o objeto "payload"

Até o momento, não chegamos a usar o segundo argumento da função redutora **(state, action)**, o objeto **action** foi esquecido. No exemplo a seguir faremos uso dele, porém, com uma chave extra chamada **payload**.

A chave **payload**, assim como no Redux, fica encarregada de despachar os dados necessários para a ação atual. Também iremos atualizar nossas funções redutoras para **receber apenas** o objeto de **payload** e não o objeto **action**. Isolando o acesso a qualquer outro tipo de dados desnecessários.

Vamos buscar dados da API do [Rick & Morty](https://rickandmortyapi.com) e montar uma lista com os nomes dos personagens.

Seguindo os exemplos acima, ficamos com o seguinte código:

```jsx
import immer from "immer";

let initialState = {
  characters: {
    data: null,
    error: null,
    loading: false
  }
};
let reducerActions = {
  fetch_rick_and_morty_pending: (state, payload) => {
    state.characters.loading = true;
    state.characters.error = null;
    state.characters.data = null;
  },
  fetch_rick_and_morty_resolved: (state, payload) => {
    state.characters.loading = false;
    state.characters.error = null;
    state.characters.data = payload.value;
  },
  fetch_rick_and_morty_rejected: (state, payload) => {
    state.characters.loading = false;
    state.characters.error = payload.error;
    state.characters.data = null;
  }
};
let reducer = (state, action) => {
  let fn = reducerActions[action.type];
  
  if (fn) {
    return immer(state, draftState => fn(draftState, action.payload));
  }

    console.log('[WARNING] Action without reducer:', action);
    return state;
};

function App() {
  let [state, dispatch] = React.useReducer(reducer, initialState);

  React.useEffect(() => {
    let didRun = true;

    async function fetchRickAndMorty() {
      let req = await fetch("https://rickandmortyapi.com/api/character");
      let json = await req.json();
      return json;
    }

    if (state.characters.loading) {
      fetchRickAndMorty()
        .then(data => {
          if (didRun) {
            dispatch({
              type: "fetch_rick_and_morty_resolved",
              payload: { value: data.results }
            });
          }
        })
        .catch(err => {
          if (didRun) {
            dispatch({
              type: "fetch_rick_and_morty_rejected",
              payload: { error: err }
            });
          }
        });
    }

    return () => {
      didRun = false;
    };
  }, [state.characters]);

  let { loading, data, error } = state.characters;

  return (
    <div className="App">
      <button
        type="button"
        onClick={() => dispatch({ type: "fetch_rick_and_morty_pending" })}
      >
        Let's Rick & Morty!
      </button>
      {loading && data === null && <p>Loading characters...</p>}
      {!loading && error !== null && <p>Ooops, something wrong happened!</p>}
      {!loading && data !== null && data.length === 0 && (
        <p>No characters to display.</p>
      )}
      {!loading && data !== null && data.length > 0 && (
        <ul>
          {state.characters.data.map(char => (
            <li key={char.id}>{char.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}
```

Como podemos ver, utilizar operações de mutação deixa tudo bem mais simples, especialmente para acessar objetos aninhados no estado.

Gerenciamento de estado é um tópico a parte, que merece sua própria discussão, mas aqui podemos ver alguns padrões de domínios, nomenclatura e ações.

Você pode conferir o exemplo ao vivo em:

https://codesandbox.io/s/live-demo-article-usereducer-fyehh

# Finalizando

React Hooks trazem algumas facilidades, mas ainda temos que tomar cuidado com muita coisa, afinal, é JavaScript! Cuidar de **valores e referências** pode ser uma dor de cabeça se você não está acostumado com nossa amada linguagem.

E aí tem alguma dica para React.useReducer? Ou React.useState? Compartilha aí nos comentários!

Até a próxima! 👋🎉