Antes do lançamento do React Hooks, utilizávamos alguns métodos de ciclo de vida para a execução de efeitos colaterais. Por exemplo, em componentes de classe para executar uma ação quando o componente é montado usamos o método componentDidMount .
componentDidMount
class MeuComponente extends React.Component {
componentDidMount() {
console.log('Componente montando!');
}
render() {
return null;
}
}
Utilizando React Hooks o equivalente seria:
const MeuComponente = () => {
useEffect(() => console.log('Componente Montado'), []);
return null;
}
No exemplo vemos o uso do Hook useEffect que torna possível aplicar efeitos colaterais em componentes funcionais. Ou seja, o Hook useEffect é como se fosse uma combinação dos métodos componentDidMount, componentDidUpdate e componentWillUnmount.
O useEffect recebe uma função como argumento que será executada por padrão após a renderização e após cada atualização. O segundo argumento é um array de valores (normalmente props). Se algum desses valores for alterado, a função será executada depois de cada renderização.
Anatomia do useEffect
A anatomia do Hook useEffect é composta da seguinte forma, como primeiro parâmetro temos uma função que será executada sempre que o componente for renderizado e uma função de retorno caso seja preciso executar uma limpeza. O segundo parâmetro é uma array que quando passado vazio faz com que o useEffect se comporte como componentDidMount, quando o array recebe uma prop ou estado o useEffect só será executado quando houver mudanças.
- Como primeiro parâmetro é passado uma função, responsável por executar as ações e se for o caso também retornar a função que fará a limpeza.
- Corpo da função onde será implementado as ações.
- Retorno de uma função responsável por fazer a limpeza quando necessário.
- Como segundo parâmetro é passado um array responsável por alterar o comportamento do useEffect.
Tipos de Efeitos Colaterais com React Hooks
Basicamente são dois tipos de efeitos colaterais em componentes React, efeitos sem limpeza e efeitos com limpeza. Vamos entender o funcionamento de cada um.
Efeitos sem limpeza
Utilizamos efeitos sem limpeza quando precisamos executar algum código adicional após a renderização do DOM. Isso inclui requisições, logs e manipulações manuais do DOM.
Buscando dados de uma API externa usando o Hook useEffect
Vamos criar um exemplo que não necessita de limpeza, quando o componente for montado vamos utilizar o useEffect para fazer uma requisição usando o axios para buscar dados em uma API externa.
No nosso componente vamos fazer a importação dos hooks useState e useEffect:
import React, {useState, useEffect} from 'react';
Em seguida vamos criar o estado para o array que irá armazenar os dados retornados da API:
function App() {
const [posts, setPosts] = useState([]);
return (
<div className="App">
<p>Request example</p>
</div>
);
}
Essa declaração pode ser um pouco estranha inicialmente, mas vamos entender o que está acontecendo. O uso dos dois colchetes envolvendo posts e setPosts é chamado de Atribuição via Desestruturação. Nessa declaração estamos criando duas novas variáveis, posts que receberá o primeiro valor retornado pelo useState e setPosts que receberá o segundo valor retornado.
Com o estado criado vamos partir para a implementação do useEffect que irá fazer a requisição. Utilizaremos a biblioteca Axios para fazer as requisições:
import axios from "axios";
A implementação do hook useEffect ficará no corpo do componente da seguinte forma:
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/posts")
.then(res => {
setPosts(res.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<div className="App">
<p>We found {posts.length} posts</p>
</div>
);
}
No corpo do hook useEffect estamos utilizando o axios para fazer uma requisição ao JSONPlaceholder, em seguida chamamos a função setPosts que irá atribuir os dados retornados ao nosso estado posts. O código implementado não necessita de limpeza pois só é feito uma única requisição durante a montagem do componente. Pode ser observado também que passamos um array vazio como segundo argumento para o useEffect, quando o segundo argumento é informado o useEffect irá se comportar como o componentDidMount, sendo executado somente na montagem do componente:
Veja o resultado completo:
Efeitos com limpeza
Alguns efeitos precisam ser limpos quando o componente for desmontado para que não haja estouro de memória, casos como subscriptions e ID de temporizadores são exemplo de recursos que precisam ser limpos.
Implementando a exibição de data/hora em tempo real
Vamos ver essa necessidade na prática com a implementação de um componente que exibe a data e hora em tempo real. Como visto anteriormente é preciso importar os hooks useState e useEffect:
import React, { useState, useEffect } from 'react';
Em seguida vamos criar uma variável de estado que vai conter o valor da data e hora:
const [dataHora, setDataHora] = useState(Date.now());
Para que possamos pegar a hora em tempo real vamos precisar passar para o useEffect a função setInterval:
useEffect(() => {
var id = setInterval(() => {
setDataHora(new Date());
}, 1000);
});
No código temos a declaração da variável id que será usada para a limpesa. Na função setInterval estamos chamando a função setDataHora que irá criar uma nova data e o valor será atribuído a variável dataHora, como segundo argumento a função setInterval recebe o tempo de cada execução, passamos 1000 milessegundo para que a função setDataHora seja executada a cada segundo. Para que seja feita a limpeza do temporizador criado, é preciso que o useEffect retorne uma função que chama clearInterval passando como parâmetro o id do temporizador criado:
useEffect(() => {
var id = setInterval(() => {
setDataHora(new Date());
}, 1000);
return () => {
clearInterval(id);
};
});
Com essa implementação temos a data e hora sendo criadas a cada segundo e a limpeza sendo executada corretamente. Agora só é preciso usar a API de internacionalização do EcmaScript para formatar a exibição no corpo do componente:
return (
<div className="App">
{Intl.DateTimeFormat("pt-BR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric"
}).format(dataHora)}
</div>
);
E teremos a exibição da data hora correta com atualização a cada segundo:
Resultado completo da implementação: