Domine React Hooks - Do Básico ao Avançado

intermediário | 60 min leitura | 2024.12.15

O que Você Aprenderá Neste Tutorial

✓ useState - Fundamentos de gerenciamento de estado
✓ useEffect - Tratamento de efeitos colaterais
✓ useContext - Compartilhamento de estado global
✓ useReducer - Gerenciamento de estado complexo
✓ useMemo/useCallback - Otimização de performance
✓ Custom Hooks - Reutilização de lógica

Pré-requisitos

  • Conhecimento básico de JavaScript
  • Entendimento básico de React (componentes, props)
  • Node.js instalado

Configuração do Projeto

# Criar projeto
npx create-react-app hooks-tutorial
cd hooks-tutorial

# Iniciar servidor de desenvolvimento
npm start

Passo 1: useState - Fundamentos de Gerenciamento de Estado

Uso Básico

// src/components/Counter.jsx
import { useState } from 'react';

export default function Counter() {
  // [valor do estado, função de atualização] = useState(valor inicial)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Contagem: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Incrementar
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrementar
      </button>
      <button onClick={() => setCount(0)}>
        Resetar
      </button>
    </div>
  );
}

Gerenciamento de Estado de Objeto

// src/components/UserForm.jsx
import { useState } from 'react';

export default function UserForm() {
  const [user, setUser] = useState({
    nome: '',
    email: '',
    idade: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    // Use spread operator para preservar valores existentes
    setUser(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input
        name="nome"
        value={user.nome}
        onChange={handleChange}
        placeholder="Nome"
      />
      <input
        name="email"
        value={user.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="idade"
        type="number"
        value={user.idade}
        onChange={handleChange}
        placeholder="Idade"
      />
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </form>
  );
}

Passo 2: useEffect - Tratamento de Efeitos Colaterais

useEffect Básico

// src/components/Timer.jsx
import { useState, useEffect } from 'react';

export default function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    let interval = null;

    if (isRunning) {
      interval = setInterval(() => {
        setSeconds(prev => prev + 1);
      }, 1000);
    }

    // Função de limpeza
    return () => {
      if (interval) clearInterval(interval);
    };
  }, [isRunning]); // Executa quando isRunning muda

  return (
    <div>
      <p>Tempo decorrido: {seconds} segundos</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Parar' : 'Iniciar'}
      </button>
      <button onClick={() => setSeconds(0)}>Resetar</button>
    </div>
  );
}

Busca de Dados

// src/components/UserList.jsx
import { useState, useEffect } from 'react';

export default function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setLoading(true);
        const response = await fetch(
          'https://jsonplaceholder.typicode.com/users'
        );
        if (!response.ok) throw new Error('Falha ao buscar dados');
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // Array vazio = executa apenas na montagem

  if (loading) return <p>Carregando...</p>;
  if (error) return <p>Erro: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

Passo 3: useContext - Compartilhamento de Estado Global

Criando e Usando Context

// src/context/ThemeContext.jsx
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme deve ser usado dentro de ThemeProvider');
  }
  return context;
}

Passo 4: Custom Hooks - Reutilização de Lógica

useLocalStorage

// src/hooks/useLocalStorage.js
import { useState, useEffect } from 'react';

export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

useFetch

// src/hooks/useFetch.js
import { useState, useEffect } from 'react';

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, {
          signal: abortController.signal
        });
        if (!response.ok) throw new Error('Erro de rede');
        const json = await response.json();
        setData(json);
        setError(null);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => abortController.abort();
  }, [url]);

  return { data, loading, error };
}

Melhores Práticas

1. Dicas de useState
   - Mantenha o estado mínimo
   - Derive valores através de computação, não useState
   - Use spread operator ao atualizar objetos

2. Dicas de useEffect
   - Configure arrays de dependência corretamente
   - Não esqueça funções de limpeza
   - Cuidado com loops infinitos

3. Otimização de Performance
   - Evite otimização excessiva
   - Meça antes de otimizar
   - Use memo, useMemo, useCallback apenas onde necessário

4. Custom Hooks
   - Use para reutilização de lógica
   - Nomes devem começar com "use"
   - Siga o princípio de responsabilidade única

Resumo

React Hooks permitem que componentes funcionais gerenciem estado e efeitos colaterais. Após dominar os básicos useState e useEffect, avance para gerenciamento de estado complexo com useContext e useReducer, e otimize performance com useMemo e useCallback.

← Voltar para a lista