React - Hooks

Imagem para React - Hooks

ReactPostado em  6 min de leitura

A versão 16.8.0, é o primeiro release que traz suporte estável aos Hooks. Com isso, simplificando a vida de programadores e curiosos. Permitindo o uso de estado e outros recursos da biblioteca sem escrever uma classe.

Regras dos Hooks

  • Apenas chame os Hooks no nível mais alto. Não chame Hooks dentro de loops, condicionais ou funções aninhadas. O React identifica os hooks de um componente pela ordem em que eles foram chamados.
  • Apenas chame os Hooks em componentes funcionais. Não chame Hooks em funções JavaScript comuns. (Há apenas um outro lugar válido para se chamar Hooks — dentro dos seus próprios Hooks customizados.)

Confira o link para o plugin do eslint, para que as regras sejam revisadas automaticamente.

Tipos de Hooks

  • Hook de estados - useState - permite ler e armazenas as informações de maneira mais fácil e prática no estado, eliminando alguns componentes de classes e substituindo por componentes funcionais.
  • Hook de efeitos - useEffect - utilizado para executar funções que necessitam ou realizam algum efeito no componente. Ex: mutations, subscriptions, timers e logging. Tem o mesmo efeito que os componentDidMount e componentDidUpdate tem nas classes.
  • Hook customizado - Crie seus próprios hooks e extraia a lógica de um componente em funções reutilizáveis.
  • Outros Hooks - Hooks nativos com funções específicas.

Os Hooks existentes podem ser classificados como básicos e adicionais. Veja a lista abaixo:

Hooks básicos:

Hooks adicionais:

Hook de estados

Vamos olhar o hook de estado. Abaixo veremos um exemplo:

import React, { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      Você clicou {count} vezes!
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

O hook nesse caso é o useState. Ele está recebendo um estado inicial e retorna um array com dois valores. Sendo o primeiro valor o estado atual e o segundo uma função para atualizar esse estado. O useState não tem a funcionalidade idêntica ao setState que é utilizado em classes. Quando se é passado um objeto para o setState, o mesmo combina o valor que estamos passando com o antigo. Já no useState, todo o estado do hook será alterado, mas temos o mesmo efeito usando o operador de spread. Ex: useState({ ...oldState, ...newState }); .

Exemplo de um objeto no estado inicial:

function Counter() {
  const [state, setState] = useState({ nome: '', idade: 0  })
  ...
  )
}

Também, podemos chamar mais de uma vez o nosso hook useState no nosso componente.

function Counter() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)
  ...
  )
}

Hook de efeitos

Agora vamos falar do hook useEffect. O mesmo permite que seu componente em forma de função tenha acesso aos métodos de ciclo de vida sem precisar refatorar seu componente para uma classe. Abaixo, um exemplo:

import React, { useState, useEffect } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    window.document.title = `Você clicou ${count} vezes!`
  })

  return (
    <div>
      Você clicou {count} vezes!
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
)}

O título será alterado de acordo com a atualização do estado do componente. O useEffect nesse contexto, seria o mesmo que o ComponentDidMount e também o ComponentDidUpdate. Será chamada a função passada tanto quando o componente é montado quando é atualizado.

O useEffect te ajuda ao desmontar os recursos, exatamente como faria com ComponentWillUnmount.

function Example() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);

  useEffect(() => {
    const mouseMove = e => {
      setX(e.screenX)
      setY(e.screenY)
    }

    document.addEventListener('mousemove', mouseMove);
    return () => document.removeEventListener('mousemove', mouseMove);
  })

  return <div>O Mouse esta no {x}, {y}</div>;
}

Acima temos o evento de mousemove configurado para alterar o estado do componente de acordo com o movimento do mouse e quando o componente for desmontado será rodado o removeEventListener. Também será chamada quando for detectado que o useEffect precisa rodar novamente, ou seja em cada render. A cada alteração no estado do componente nosso evento está sendo removido e adicionado novamente. Agora não queremos isso e precisamos que o evento seja adicionado na montagem e na desmontagem.

Vamos utilizar o segundo argumento que o useEffect recebe, que é uma lista dos valores que devem mudar para que ele rode novamente. Passando uma lista vazia, ele irá rodar apenas quando é montado e a função de limpeza apenas quando é desmontado.

function Example() {
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  useEffect(() => {
    const mouseMove = e => {
      setX(e.clientX)
      setY(e.clientY)
    }

    document.addEventListener('mousemove', mouseMove);
    return () => document.removeEventListener('mousemove', mouseMove);
  }, []) // <-- lista vazia

  return <div> Mouse esta no {x}, {y}</div>;
}

Os Event listeners serão chamados apenas quando precisamos. O segundo parâmetro pode ser utilizado para dizer quando nosso efeito vai rodar. Abaixo, um exemplo:

function Status(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => setIsOnline(status.isOnline)

    API.subscribeToUserStatus(props.user.id, handleStatusChange)

    return () => API.unsubscribeFromUserStatus(props.user.id, handleStatusChange)

  }, [props.user.id]) // apenas se desinscreve caso props.friend.id mude
}

Quando friend.id for alterado, iremos chamar o unsubscribeFromUserStatus com id anterior e depois chamar o subscribeToUserStatus com id atual, assim temos consistência na limpeza dos recursos de forma simples.

Hook customizado

Os Hooks são totalmente desacoplados de componentes, o que nos permite combiná-los para criar novos hooks mais específicos e compartilhar lógica entre nossos componentes.

Começaremos com o exemplo abaixo:

import React, { useState, useEffect } from 'react';

function Status(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    API.subscribeToUSerStatus(props.user.id, handleStatusChange)

    return () => API.unsubscribeFromUSerStatus(props.user.id, handleStatusChange)
    }
  })

  if (isOnline === null) return 'Loading...';

  return isOnline ? 'Online' : 'Offline';
}

Também vamos precisar de uma lista de contatos e exibir seus respectivos status.

import React, { useState, useEffect } from 'react';

function UserListItem(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    ChatAPI.subscribeToUserStatus(props.user.id, handleStatusChange)
    return () =>  API.unsubscribeFromUserStatus(props.user.id, handleStatusChange)
  })

  return <li style={{ color: isOnline ? 'green' : 'black' }}>{props.user.name}</li>;
}

Com isso, temos uma repetição de código. Resolveremos isso, ao extrair a lógica repetida em um hook customizado.

import React, { useState, useEffect } from 'react';

function useUserStatus(userID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    API.subscribeToUserStatus(userID, handleStatusChange)
    return () =>  API.unsubscribeFromUserStatus(userID, handleStatusChange)
  })

  return isOnline;
}

Agora a lógica que tínhamos em nossos componentes, está em uma função separada (um padrão: que os hooks tenham o prefixo use). Abaixo, exemplo da utilização:

function UserStatus(props) {
  const isOnline = useUserStatus(props.user.id)

  if (isOnline === null) return 'Loading...';

  return isOnline ? 'Online' : 'Offline';
}
function UserListItem(props) {
  const isOnline = useUserStatus(props.user.id)

  return <li style={{ color: isOnline ? 'green' : 'black' }}>{props.user.name}</li>;
}

Agora temos uma lógica simplificada. Também é possível criar hooks para lidar com bibliotecas externas:

import React, { useState, useEffect } from 'react';

const useObservable = (observable, initialValue) => {
  const [value, setValue] = useState(initialValue)
  useEffect(() => {
    const subscription = observable.subscribe({next: setValue})
    return () => subscription.unsubscribe()
  }, [observable])
  return value
}

Acima, a cada novo evento no stream do observable temos uma atualização no estado. Abaixo, um exemplo usando a bilbioteca RxJS.

import React from 'react';
import { fromEvent } from 'rxjs';
import { map }  from 'rxjs/operators';
import { useObservable } from './observableHook';

const mouseTest = fromEvent(document, 'mousemove').pipe( map(e => [e.clientX, e.clientY]) );

const App = () => {
   const [x,y] = useObservable(mouseTest, [0,0]);

   return <div>Mouse x:{x} y:{y}</div>;
}

Considerações finais

Não foi falado muito sobre os Outros Hooks, mas podemos ver a listagem no ínício deste artigo. Mais sobre os Hooks, podem ser vistos na documentação oficial e em próximos artigos.