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.