React - Hooks

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:

1
2
3
4
5
6
7
8
9
10
11
12
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:

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

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

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
function UserStatus(props) {
const isOnline = useUserStatus(props.user.id)

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

return isOnline ? 'Online' : 'Offline';
}
1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
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.

1
2
3
4
5
6
7
8
9
10
11
12
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.

Relacionados

Ao fechar este aviso ou continuar navegando no site Nerd Calistênico, você aceita o uso de cookies.

Este site usa cookies para assegurar a melhor experiência para os nossos usuários. Consulte nossa política de privacidade.

Uma nova versão está disponível. Clique aqui para atualizar.