React Hooks: Como utilizar, motivações e exemplos práticos

A funcionalidade de Hooks trazida a partir da versão 16.7.0 do React visa basicamente oferecer formas de trabalharmos com estado e outras API's sem a necessidade de ter uma classe (stateful component).

Antes de falarmos mais sobre as motivações por trás dessa feature ou até mostrar exemplos mais complexos de como os hooks vão ajudar no nosso dia-a-dia, vamos entender um exemplo básico com um pouco de código.

React Native na prática - Evento online | Rocketseat
Crie dois projetos mobile para Android e iOS com uma única base de código em duas aulas 100% práticas.

Imagine que você tenha um Modal, no clique de um botão o modal deve aparecer, no clique de outro botão esse modal deve desaparecer. Utilizando a abordagem tradicional do React teríamos algo assim:

class App extends Component {
  state = {
    modalOpen: false,
  }

  render() {
    return (
      <div>
        <button onClick={() => this.setState({ modalOpen: true })}>
          Exibir modal
        </button>
        <button onClick={() => this.setState({ modalOpen: false })}>
          Remover modal
        </button>

        { this.state.modalOpen && <Modal /> }
      </div>
    );
  }
}

Essa seria a abordagem mínima para conseguirmos controlar a aparição do modal em tela utilizando o estado do React. Quando utilizamos os hooks nós simplesmente evitamos a verbosidade desnecessária que toda essa criação do state nos dá, ficando com um código como o seguinte:

import { useState } from 'react';

const App = () => {
  const [modalOpen, setModalOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setModalOpen(true)}>
        Exibir modal
      </button>
      <button onClick={() => setModalOpen(false)}>
        Remover modal
      </button>

      { modalOpen && <Modal /> }
    </div>
  );
}

Veja que utilizamos o hook useState para declarar um estado em um componente criado em formato de função. A função useState recebe por parâmetro o valor padrão do nosso estado e retorna o valor do estado em si e uma função que altera esse valor.

Motivações

Até então, a forma mais tradicional de compartilharmos funcionamento entre componentes era pelos patterns já conhecidos, os HOC's (Higher-order components) e as render props.

React Native na prática - Evento online | Rocketseat
Crie dois projetos mobile para Android e iOS com uma única base de código em duas aulas 100% práticas.

O grande problema desses padrões é que você precisa modificar boa parte do código do componente para que o mesmo se adapte ao funcionamento compartilhado, aumentando sua verbosidade e perdendo boa parte do isolamento de responsabilidade, ou seja, você acaba perdendo qual parte do código faz o que.

Veja um exemplo de utilização de dois render props em um único componente:

export default class  extends Component {
  render() {
    return (
      <Theme>
        {theme => (
          <I18N>
            {i18n => (
              <Header theme={theme} language={i18n.getLanguage()} />
            )}
          </I18N>
        )}
      </Theme>
    );
  }
}

A verbosidade desse pattern torna o código praticamente ilegível, ainda mais se tivermos um crescimento de responsabilidades desse componente com o tempo.

Além disso, os dois patterns utilizados anteriormente acabavam criando muitos níveis na renderização final do React tornando impossível uma visualização de árvore para encontrar o que é o que, veja um exemplo do site da Netflix que utiliza React ainda sem os hooks:

Veja que os elementos AppContextProvider, ConnectToApps, LayoutContext, DetectFonts são tudo fruto de contextos criados através dos patterns de HOC's e Render Props (com o hooks não precisaríamos de nenhum deles).

Existem outras motivações por trás do hook mas a maioria gira na complexidade e verbosidade desnecessária em torno dos patterns já existentes principalmente quando os componentes ficam maiores.

Exemplos

Para entendermos na prática como os hooks funcionam vamos analisar alguns conceitos simples de utilização desse novo pattern:

useState

O hook mais comum utilizado para controlarmos alguma variável de estado dentro de um functional component no React. Para utilizar definimos:

const [count, setCount] = useState(0);

O primeiro valor count representa o valor do estado que será manipulado pela função setCount recebida através da desestruturação realizada no useState. O valor 0 repassado ao hook é o valor inicial do estado.

Então, para manipularmos o valor de count podemos simplesmente executar:

<button onClick={() => setCount(count + 1)}>+</button>

useEffect

Uma das grandes deficiência dos funcional components sempre foi lidar com side-effects como chamadas à API, tarefas assíncronas, modificações na DOM, etc. Com o hook useEffect podemos operar efeitos colaterais durante a renderização do nosso componente.

Imagine que no exemplo acima do count gostaríamos de atualizar o título de página toda vez que a informação de count atualizar. Dentro do nosso funcional component (antes do return) definimos:

useEffect(() => {
  document.title = `Você clicou ${count} vezes.`
}, [count])

Veja que o hook recebe como primeiro parâmetro uma função (assíncrona ou não) que é executada após inicialização e atualização do componente, mais ou menos o que temos com o componentDidMount e componentDidUpdate no stateful component.

O segundo parâmetro [count] indica em quais situações esse effect deve executar, nesse caso ele só executará caso o valor de count alterar. Podemos também não repassar esse parâmetro e indicaremos ao hook que deve executar na inicialização e em todas atualizações do componente, independente de seus valores alterarem.

Outra dica é que você pode passar um array vazio [] ao hook como segundo parâmetro garantindo que o mesmo irá executar apenas uma vez na inicialização do componente (tipo um componentDidMount):

const Repositories = () => {
  const [repositories, setRepositories] = useState([]);
  
  useEffect(() => {
    async function loadRepositories() {
      const response = await axios.get('https://api.github.com/orgs/rocketseat/repos');

      setRepositories(response.data);
    }
    
    loadRepositories();
  }, []);

  return (...);
};

Veja que criamos uma nova função loadRepositories dentro do useEffect, isso se deve basicamente porque não é uma boa prática adicionarmos um async à função que o useEffect recebe como parâmetro.

useContext

Para utilizarmos a Context API do React até então precisávamos utilizar o pattern de render props criando um código como o seguinte:

<ThemeContext.Consumer>
  { theme => (
    // Aqui temos o tema
  ) }
</ThemeContext.Consumer>

Mas agora com os hooks podemos simplesmente fazer o seguinte:

const theme = useContext(ThemeContext)

Quer mais?

Existem vários outros hooks nativos do React e outros milhões já criados pela comunidade, se quiser continuar lendo sobre alguns exemplos veja:

Morte do Redux?

Com a vinda dos hooks, e do hook useReducer novamente surgiram boatos sobre o fim do Redux, mas calma, isso não vai acontecer. Inclusive o criador do Redux está diretamente envolvido com a criação desse pattern de hooks e já estão provendo uma API para lidar com hooks :)

Eu já falei isso antes, mas é bom reforçar, o Redux não é uma biblioteca de compartilhamento de estado apenas, e sim uma ferramenta de gerenciamento de estado para organizar o fluxo de operações e side-effects que acontece em sua aplicação o tempo todo.

React Native na prática - Evento online | Rocketseat
Crie dois projetos mobile para Android e iOS com uma única base de código em duas aulas 100% práticas.

Partiu hooks?

Impossível que você chegou até aqui e não esteja com vontade de botar os hooks em ação. Eu particularmente achei fantástica essa nova API e não tenho dúvidas que todo projeto deve começar a utilizar esse novo pattern o quanto antes!

Meus olhos estão assim pelos hooks!

Ah, não esquece de deixar um comentário aí em baixo sobre o que você achou desse post e também desse novo conceito do React :)