React Hooks foram lançados há quase dois anos na versão 16.8 do React. Não é muito comum ver o uso de todos eles mas eles podem ser muito úteis para simplificar uma funcionalidade, melhorar a perfomance em nossa aplicação, então, eu gostaaria de explicar e dar alguns exeplos do uso de todos React Hooks.

Nesse artigo estarei passando por todos os React Hooks.

  • useState
  • useEffect
  • useRef
  • useContext
  • useReducer
  • useMemo
  • useCallback
  • useDebug
  • useLayoutEffect

useState

O mais simples e usado dos React Hooks, o useState permite armazenar qualquer valor em um componente, como string, object, array, boolean, number, null.

Ele tem a mesma função do this.state em um antigo Class Componentes do React.

Você pode definir seu valor passando um valor direto ou uma função que acessa seu valor atual e retornando um novo.

import { useState } from 'react';

export default function Component() {
  const [counter, setCounter] = useState(0);
  return (
    <>
      <p>Clicks: {counter}</p>
      <button onClick={() => setCounter((prev) => ++prev)}>Increase 1</button>
    </>
  );
}

useEffect

O useEffect te permite executar uma função em alguns momento durante o lifecicle de um componente:

  • Na primeira renderização do componente.
  • Quando um valor observado é alterado.
  • Quando o componente é desmontado.

Ele é similiar aos métodos componentDidMount, componentDidUpdate e componentWillUnmount de um Class Componente.

import { useEffect } from 'react';

export default function Component() {
  const fetchSomething = (params) => {};
  const onResizeScreenHandler = () => {};

  useEffect(() => {
    fetchSomething(filters);
  }, [filters]);

  useEffect(() => {
    document.addEventlistener('resize', onResizeScreenHandler);
    return () => document.removeEventlistener('resize', onResizeScreenHandler);
  }, []);
}

Para executar o useEffect só uma vez, deixe o segundo parâmetro, o array vazio.

useEffect(fn, []);

Para observar algum valor, adicione ele no array do segunddo parâmetro.

useEffect(fn, [x]);

A função que você retorna irá ser chamada quando o componente for desmontado.

useEffect(() => {
  return () => doSomething();
}, []);

useRef

O useRef te permite:

  • Acessar um elemento do DOM.
  • Armazenar um valor imutável durante o ciclo de vida do componente.

Seu valor é acessado por .current:

const time = useRef(0);
console.log(time.current); // 0;

Acessando um elemento do DOM.

import { useRef, useEffect } from 'react';

export default function Component() {
  const textRef = useRef(null);
  useEffect(() => {
    if (textRef) {
      const elementWidth = textRef.current.offsetWidth;
    }
  }, [textRef]);

  return <p ref={textRef}>Exemplo de texto</p>;
}

Delay effect

Você pode usar o useRef pra criar um delay de x segundos pra chamar uma função, por exemplo, quando usuário termina de digitar um campo para não sobrecarregar sua API com várias requisições cada vez que ele digita ou apaga uma letra.

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

export default function Form() {
  const [text, setText] = useState('');
  const timeToCallSomething = useRef(null);

  const fetchSomething = () => {
    // Realiza alguma requisição http.
  };

  useEffect(() => {
    if (timeToCallSomething.current) {
      clearInterval(timeToCallSomething.current);
    }
    timeToCallSomething.current = setTimeout(fetchSomething, 1000);
    return () => clearInterval(timeToCallSomething.current);
  }, [text]);

  const onChangeHandler = ({ target: { value } }) => {
    setText(value);
  };

  return <input value={text} onChange={onChangeHandler} />;
}

Passando ref via props

Se você quiser passar uma ref para utilizar num componente filho via props, você precisará usar o método React.forwardRef do React no componente filho para receber essa ref como segundo parâmetro do componente.

import { useRef, useEffect, forwardRef } from 'react';

export default function Parent() {
  const childRef = useRef(null);

  return <Text ref={childRef} />;
}

const Text = forwardRef((props, ref) => {
  return <p ref={ref}>Exemplo de texto</p>;
});

useReducer

Esse hook é uma alternativa ao useState e bem parecido com a idéia de reducer do Redux. Ele observa um type pré definido a retorna algum valor dependendo desse type.

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, 0);
  return (
    <>
      Count: {state}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

useContext

O hook useContext te permite armazenar e acessar algum valor em qualquer lugar dentro de sua hierarquia. Ele é muito parecido com o Redux, inclusive eu já escrevi um artigo mostrando como substituir o Redux por React Hooks.

Isso é útil quando você utiliza muito um valor e não quer ficar passando ele para os componentes filhos via props.

// hooks/useUserContext.js
import { useContext, createContext } from 'react';

// Nosso contexto
const UserContext = createContext({ name: '' });

// Componente que recebe e atualiza o valor de name
export function UserContextProvider({ name, children }) {
  return <UserContext.Provider value={name}>{children}</UserContext.Provider>;
}

// Custom hook para pegar o valor atual
export default function useUserContext() {
  return useContext(UserContext);
}
// YourAppTree.js
import { UserContextProvider } from '~hooks/useUserContext';

export default function App() {
  return (
    <UserContextProvider value="John">
      <YourAppTree />
    </UserContextProvider>
  );
}
// App.js
import useUserContext from '~hooks/useUserContext';

export default function YourAppTree() {
  const userName = useUserContext(); // Inicialmente será '' e logo em seguida 'John'.
  return <p>Olá, {userName}</p>;
}

Os próximos React Hooks, useMemo e useCallback, são mais para resolver problemas de perfomances no React.

useMemo

Você pode utilizar o useMemo para economizar tempo e processamento na execução de uma função complexa.

Imagine que você tenha uma função que receba alguns parâmetros e faça um cálculo usando esses parâmetros. O que o useMemo faz é memorizar os parâmetros passados e o valor retornado da função. Assim, quando esses parâmetros forem iguais novamente, ele irá retornar o valor anterior, sem precisar calcula-lo.

import { useMemo } from 'react';

export default function Component() {
  const value = useMemo(() => {
    // Faz algo complexo e demorado.
  }, [x, y]);
}

useCallback

Antes de explicar o useCallback, gostaria de explicar um conceito importante no React.

React utiliza Strict Equality Comparison para comparar os valores das props, ou [deps] de algum React Hook. Caso algum valor seja diferente, ele atualiza o componente, ou executa o React Hook.

E você e o Patrick devem saber que as coisas não são tão óbvias no javascript com valores não primitivos.

alt

Valores primitivos são do tipo string, number e boolean. Agora os array, object e function não são primitivos, portanto, se você compara-los, irá receber false:

1 === 1; // true;
"batman" === "batman"; // true;
false === false; // true;

{} === {}; // false;
[] === []; // false;
() => {} === () => {}; //false;

Só retornará true se tiver a mesma referência, como se estiver alocado numa variável:

const fn = () => {};
fn === fn; // true;

Então se você passar uma função via props, o React irá atualiza-lo mesmo que as props não tenham mudado, simplesmente porque uma função é diferente de uma função, mesmo que seja ela mesma. complexo?

Então usamos o useCallback pra criar uma referência única pra uma função e o React saber que é ela mesma e evitar atualizações desnecessárias.

Sua referência só será diferente caso seus parâmetros mudarem.

import { useCallback } from 'react';

export default function Parent() {
  const onFetchAlwaysHandler = useCallback(() => {
    // Sua referência irá mudar quando a e b mudarem.
  }, [a, b]);

  const onFetchOnceHandler = useCallback(() => {
    // Sempre terá a mesma referência.
  }, []);

  return <Approvad onFetchHandler={onFetchHandler} />;
}

React.memo

Você também pode usar useCallback e React.memo para evitar que todo componente filho seja rerenderizado desnecessariamente sempre que houver alguma atualização no componente pai.

function List({ item, onSelect }) {
  return (
    <li>
      <button onClick={() => onSelect(item)}>Select {item}</button>
    </li>
  );
}

const MemorizedList = React.memo(List);

function App() {
  const onSelectHandler = useCallback((selectedItem) => {
    // Faz algo com selectedItem.
  }, []);

  return (
    <ul>
      {array.map((e) => (
        <MemorizedList item={e} onSelect={onSelectHandler} />
      ))}
    </ul>
  );
}

useLayoutEffect

O hook useLayoutEffect tem a mesma função do useEffect, porém, ela é executada assim que o navegador termina de fazer as alterações no DOM.

Você pode utilizar esse método para ler algum elemento do DOM ou realizar uma ação sincrona, assim que a página carrega.

import { useState, useLayoutEffect } from 'react';

function Component() {
  const [loadedDOM, setLoadedDOM] = useState(false);

  useLayoutEffect(() => {
    setLoadedDOM(true);
    window.localstorage.getItem('...');
  }, []);
}

Eu sinceramente nunca usei esse hook num projeto.

SSR

Se você estiver usando Server Side Render (NextJS), os hooks useLayoutEffect e useEffect não funcionaram, pois ele só são executados no client-side. Caso contrário, o React irá te avisa no console que você está usando no lado do server.

Você pode usa-los para interagir com o localstorage, por exemplo, que é uma feature somente do browser.

useDebugValue

O useDebugValue pode ser usado para exibir alguma label dentro de um custom hook. Ele é parecido com o console.log, porém, sua mensagem irá aparece no React DevTools.

import { useDebugValue, useState } from 'react';

function useUserStatus() {
  const [isLogged, setIsLogged] = useState(false);

  //...

  useDebugValue(isLogged ? 'Logged' : 'Not logged');

  return isLogged;
}

useImperativeHandle

O hook useImperativeHandle permite customizar o valor de uma ref do elemento pai, através do componente filho. Ele deve ser utilizado junto com o React.forwardRef no elemento filho, para receber a ref do pai.

import { useImperativeHandle, forwardRef, useEffect, useRef } from 'react';

const Input = forwardRef((props, parentRef) => {
  const childRef = useRef();
  const someMethod = () => 1;

  useImperativeHandle(parentRef, () => ({
    focusOnChildInput: () => childRef.current.focus(),
    callChildMethod: someMethod,
  }));
  return <input ref={childRef} />;
});

const Form = () => {
  const ref = useRef();
  useEffect(() => {
    if (ref) {
      ref.current?.callChildMethod?.(); // 1
      ref.current?.focusOnChildInput?.();
    }
  }, []);

  return (
    <form>
      <Input ref={ref} />
    </form>
  );
};

Documentação

Caso você queira vêr mais detalhes e exemplos dos React Hooks, veja a documetação oficial do React.