Executando verificação de segurança...
1

Construindo os seus próprios Custom React Hooks (Hooks personalizados do react)

Custom React Hooks: o que comem, como nascem, vamos descobrir?

Resumo

Nessa publicação, vou discutir um pouco sobre a utilidade dos Custom Hooks no React.js, como e quando criá-los. Além disso, alguns exemplos de suas aplicações serão fornecidos.

Introdução

Custom Hooks são funções auxiliares que só podem ser usadas dentro de componentes React, eles funcionam como uma forma de compartilhar lógica entre nossos componentes, permitindo-nos 'enxugar' um pouco do nosso código, tornando-o mais limpo e fácil de manter.

Como eles funcionam exatamente?

Quando pensamos em tornar o código mais limpo, na maioria das vezes, estamos procurando uma forma de reutilizar lógica que já foi escrita, como a inicialização de estado para nosso formulário e suas diversas funções.

Apresentando o problema:

Vamos supor que queremos uma aplicação onde temos alguns itens em uma lista, e queremos poder alterar suas quantidades. Podemos escrever um esboço da seguinte forma, abaixo está um componente que renderiza uma lista com os nomes dos nossos itens e suas quantidades:


function ListaDeItens() {
	const bananas = 0;
	const peixes = 0;
	return (
		<ul>
			<li>`Bananas: ${ bananas }`</li>
			<li>`Peixes: ${ peixes }`</li>
		</ul>
	);
}

export default ListaDeItens;

Ok, mas este componente ainda não é dinâmico. Temos uma maneira bem fácil de resolver isso simplesmente usando um hook nativo do React.js, useState, que retorna um array onde o primeiro elemento é o valor e o segundo é a função para sobrescrevê-lo, [valor, setValor].


function ListaDeItens() {
	const [bananas, setBananas] = useState<number>(0);
	const [peixes, setPeixes] = useState<number>(0);
	return (
		<ul>
			<li>`Bananas: ${ bananas }`</li>
			<li>`Peixes: ${ peixes }`</li>
		</ul>
	);
}

export default ListaDeItens;

Ótimo, agora podemos começar a manipular os valores dos nossos itens através de seus estados. E para isso, precisamos de algumas funções auxiliares e botões.


function ListaDeItens() {
	const [bananas, setBananas] = useState<number>(0);
	const [peixes, setPeixes] = useState<number>(0);
	
	const diminuirBananas = () => setBananas((anterior) => anterior - 1);
	const aumentarBananas = () => setBananas((anterior) => anterior + 1);
	
	const diminuirPeixes = () => setPeixes((anterior) => anterior - 1);
	const aumentarPeixes = () => setPeixes((anterior) => anterior + 1);

	return (
		<ul>
			<li>
				Bananas:
				<button onClick={ diminuirBananas }>-</button>
				<span>{ bananas }</span>
				<button onClick={ aumentarBananas }>+</button>
			</li>
			<li>
			Peixes:
				<button onClick={ diminuirPeixes }>-</button>
				<span>{ peixes }</span>
				<button onClick={ aumentarPeixes }>+</button>
			</li>
		</ul>
	);
}

export default ListaDeItens;

Talvez você já consiga ver a direção que nosso código está tomando, e certamente você poderia fazer algo mais limpo e interessante sem a necessidade de custom hooks.

Ainda assim, qual é o grande problema com essa abordagem? Bem, para cada item que adicionamos, nosso código ganha mais 3 linhas, que seriam idênticas se não fosse pelos nomes diferentes. Além disso, por exemplo, se agora precisássemos evitar que o número fosse negativo como -1 ou -2, teríamos que mudar isso em várias linhas.

É exatamente aí que vamos reciclar essa lógica com a ajuda de custom hooks.

Resolvendo o problema:

Antes de construirmos nosso custom hook, vou apontar alguns detalhes:

  • Por convenção, todo hook deve começar com a palavra minúscula use;
  • Custom hooks são funções que contêm outros hooks dentro;
  • Hooks devem retornar alguns valores úteis, como um estado inicializado e/ou funções que o alteram;

Agora que temos isso em mente, vamos começar a construí-lo.

function useContadorDeItens() {
	// inicializando nosso estado
	const [quantidade, setQuantidade] = useState<number>(0);
	
	// Nossa lógica para incrementar o valor em 1
	const aumentar = () => {
	  setQuantidade((anterior) => anterior + 1);
	};
	
	// Nossa lógica para decrementar o valor em 1 impedindo que seja negativo
	const diminuir = () => {
	  if(quantidade - 1 < 0) return;
	  setQuantidade((anterior) => anterior -1);
	};

	return [quantidade, aumentar, diminuir];
}

Agora sim! Este custom hook nos dá controle sobre todas as ações e informações que precisamos. Deixe-me explicar passo a passo.

Primeiro, inicializamos um estado que será responsável por armazenar nosso valor e disparar a re-renderização do componente toda vez que for atualizado.

Então, criamos funções que funcionarão como ações específicas para controlar nosso estado.

Finalmente, retornamos os valores que precisamos; neste caso, as duas funções que controlam o estado e o próprio estado.

Agora só precisamos usá-lo em nosso exemplo


function ListaDeItens() {
	const [bananas, aumentarBananas,
	diminuirBananas] = useContadorDeItens();
	
	const [peixes, aumentarPeixes,
	diminuirPeixes] = useContadorDeItens();

	return (
		<ul>
			<li>
				Bananas:
				<button onClick={ diminuirBananas }>-</button>
				<span>{ bananas }</span>
				<button onClick={ aumentarBananas }>+</button>
			</li>
			<li>
			Peixes:
				<button onClick={ diminuirPeixes }>-</button>
				<span>{ peixes }</span>
				<button onClick={ aumentarPeixes }>+</button>
			</li>
		</ul>
	);
}

export default ListaDeItens;

Aplicando no mundo real

Agora que estamos familiarizados com hooks, o que eles comem e como nascem. Vamos ver alguns dos mais comuns!

useLocalStorage

/*
 * Esse hook te permite interagir com a
 * API de localStorage do navegador
*/
function useLocalStorage<T>(chave: string, valorInicial: T) {
	const lerValor = () => {
	  if(window === undefined) return valorInicial;
	  const valorSalvo = JSON.parse(localStorage.getItem(chave));
	  return valorSalvo || valorInicial;
	}
	
	const [valor, setValor] = useState<T>(lerValor());
	
	useEffect(() => {
		localStorage.setItem(chave, JSON.stringify(valor))
	}, [valor]);
	
	return [valor, setValor];
}

Este hook funciona de forma muito similar a um useState, já que simplesmente retorna o valor e a função para sobrescrevê-lo, com a pequena diferença de que se houver um valor salvo na chave do armazenamento local, ele será carregado na inicialização do estado, e toda vez que for atualizado, o novo valor é salvo no armazenamento local.

useFetch

/*
 * Esse hook facilita a interação com API's externas
 * abstraindo a complexidade de erros, loading e
 * dados retornados
 */
function useFetch(url: string) {
	const [carregando, setCarregando] = useState<boolean>(true);
	const [erro, setErro] = useState<string>('');
	const [dados, setDados] = useState<any>();
	

	
	useEffect(() => {
		const buscarDados = async () => {
			try {
				setCarregando(true);
				setErro('');
				const resposta = await fetch(url);
				const dadosRetornados = await resposta.json();
				setDados(dadosRetornados);
			}
			catch(err) {
				if(err instanceof Error) {
					setErro(err?.message ?? 'Erro ao buscar dados');
				}
			}
			finally {
				setCarregando(false);
			}
		};
		buscarDados();
	}, []);
	
	return [dados, carregando, erro]
}

O useBuscar é uma forma de realizar aquela busca na API que muitas vezes deixa o código extenso, e ele já retorna o status de carregamento, erro e os dados retornados pela API.

useForm

function useForm<T>(initialForm: T) {
	const [form, setForm] = useState<T>(initialForm);
	
	const handleChange = ({ target }) => {
		setForm({ ...form, [target.name]: target.value});
	}
	
	const reset = () => setForm(initialForm);
	
	return [form, handleChange, reset];
}

Melhor do que criar um estado para cada elemento do formulário é unificá-los todos em um pequeno hook. Basta passar um objeto como parâmetro e o controle do seu formulário está feito.

function FormularioLogin() {
	const initialForm = {email: '', password: ''};
	const [form, handleChange, reset] = useForm(initialForm);
	
	return (
		<form>
			<input
				type="text"
				name="email"
				value={ form.email }
				onChange={ (e) => handleChange(e) }
			/>
			<input
				type="password"
				name="password"
				value={ form.password }
				onChange={ (e) => handleChange(e) }
			/>
		</form>
	)
}

Conclusão

Custom hooks são um padrão de design no React.js que nos permitem compartilhar lógica entre componentes, tornando o código mais limpo e reutilizável.

Carregando publicação patrocinada...