Hook useEffect - Efeitos Colaterais

Módulo 3: Estado e Ciclo de Vida

Aula 2
1

Introdução ao useEffect

20:00

Entenda efeitos colaterais e o ciclo de vida em componentes funcionais

2

Padrões Comuns do useEffect

Exemplos práticos dos padrões mais usados com useEffect

Padrões Comuns do useEffect

1. Buscar Dados de uma API

function ListaUsuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [carregando, setCarregando] = useState(true);
  const [erro, setErro] = useState(null);

  useEffect(() => {
    const buscarUsuarios = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        if (!response.ok) throw new Error('Erro ao buscar dados');
        const dados = await response.json();
        setUsuarios(dados);
      } catch (err) {
        setErro(err.message);
      } finally {
        setCarregando(false);
      }
    };

    buscarUsuarios();
  }, []); // Array vazio = executa uma vez

  if (carregando) return <p>Carregando...</p>;
  if (erro) return <p>Erro: {erro}</p>;

  return (
    <ul>
      {usuarios.map(usuario => (
        <li key={usuario.id}>{usuario.name}</li>
      ))}
    </ul>
  );
}

2. Timer com Cleanup

function Relogio() {
  const [tempo, setTempo] = useState(new Date());

  useEffect(() => {
    const timer = setInterval(() => {
      setTempo(new Date());
    }, 1000);

    // Cleanup: limpa o timer quando o componente desmonta
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <h2>{tempo.toLocaleTimeString()}</h2>;
}

3. Listener de Eventos

function TamanhoJanela() {
  const [largura, setLargura] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setLargura(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    // Cleanup: remove o listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <p>Largura da janela: {largura}px</p>;
}

4. LocalStorage com Sincronização

function ConfiguracaoTema() {
  const [tema, setTema] = useState(() => {
    // Inicializa com valor do localStorage
    return localStorage.getItem('tema') || 'claro';
  });

  useEffect(() => {
    // Salva no localStorage quando tema muda
    localStorage.setItem('tema', tema);
    
    // Aplica tema ao documento
    document.body.className = tema;
  }, [tema]);

  return (
    <button onClick={() => setTema(tema === 'claro' ? 'escuro' : 'claro')}>
      Mudar para tema {tema === 'claro' ? 'escuro' : 'claro'}
    </button>
  );
}

5. Debounce de Pesquisa

function PesquisaProdutos() {
  const [termo, setTermo] = useState('');
  const [resultados, setResultados] = useState([]);

  useEffect(() => {
    // Não pesquisa se o termo estiver vazio
    if (!termo) {
      setResultados([]);
      return;
    }

    // Debounce: aguarda 500ms após parar de digitar
    const timeoutId = setTimeout(() => {
      // Simula busca de API
      console.log('Pesquisando por:', termo);
      // fetch(`/api/produtos?q=${termo}`).then(...);
    }, 500);

    // Cleanup: cancela o timeout se o usuário continuar digitando
    return () => clearTimeout(timeoutId);
  }, [termo]);

  return (
    <div>
      <input
        type="text"
        value={termo}
        onChange={(e) => setTermo(e.target.value)}
        placeholder="Pesquisar produtos..."
      />
    </div>
  );
}

6. Dependências Múltiplas

function CalculadoraPreco({ quantidade, precoUnitario }) {
  const [total, setTotal] = useState(0);
  const [desconto, setDesconto] = useState(0);

  useEffect(() => {
    // Recalcula quando quantidade OU preço mudam
    const subtotal = quantidade * precoUnitario;
    
    // Aplica desconto progressivo
    if (quantidade >= 10) {
      setDesconto(0.1); // 10% de desconto
    } else if (quantidade >= 5) {
      setDesconto(0.05); // 5% de desconto
    } else {
      setDesconto(0);
    }
    
    setTotal(subtotal * (1 - desconto));
  }, [quantidade, precoUnitario, desconto]);

  return (
    <div>
      <p>Subtotal: R$ {(quantidade * precoUnitario).toFixed(2)}</p>
      {desconto > 0 && <p>Desconto: {(desconto * 100)}%</p>}
      <p>Total: R$ {total.toFixed(2)}</p>
    </div>
  );
}

Dicas Importantes

1. Evite Loops Infinitos

// ❌ ERRADO: Causa loop infinito
useEffect(() => {
  setCount(count + 1);
}); // Sem dependências = executa sempre

// ✅ CORRETO: Executa uma vez
useEffect(() => {
  setCount(count + 1);
}, []);

2. Não Esqueça do Cleanup

// ❌ Vazamento de memória
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
}, []);

// ✅ Com cleanup
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer);
}, []);

3. Use Dependências Corretas

// ⚠️ Aviso do ESLint sobre dependências faltando
useEffect(() => {
  console.log(count); // usa 'count' mas não lista como dependência
}, []); // ESLint avisará sobre isso

// ✅ Correto
useEffect(() => {
  console.log(count);
}, [count]);
3

Exercício: Relógio com Fuso Horário

Crie um relógio que atualiza em tempo real com seleção de fuso horário

Activity

Exercício: Relógio com Fuso Horário

Objetivo

Criar um componente de relógio que:

  • Mostra a hora atual
  • Atualiza a cada segundo
  • Permite selecionar diferentes fusos horários
  • Limpa corretamente o timer quando necessário

Requisitos

  1. Use useState para armazenar:
    • Hora atual
    • Fuso horário selecionado
  2. Use useEffect para:
    • Criar um timer que atualiza a cada segundo
    • Limpar o timer quando o componente desmontar
    • Reagir a mudanças no fuso horário
  3. Implemente um select com pelo menos 4 fusos horários
  4. Formate a hora de forma legível

Template Inicial

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

function RelogioFusoHorario() {
  // Fusos horários disponíveis
  const fusos = [
    { nome: 'Brasília', offset: -3 },
    { nome: 'Nova York', offset: -5 },
    { nome: 'Londres', offset: 0 },
    { nome: 'Tóquio', offset: 9 }
  ];

  // Seu código aqui

  return (
    <div>
      {/* Implemente a interface aqui */}
    </div>
  );
}

export default RelogioFusoHorario;

Dicas

  • Use new Date() para obter a hora atual
  • Para ajustar o fuso horário, use:
    const horaUTC = data.getTime() + (data.getTimezoneOffset() * 60000);
    const horaFuso = new Date(horaUTC + (3600000 * offset));
    
  • Use toLocaleTimeString() para formatar a hora
  • Não esqueça de retornar a função de cleanup no useEffect

Solução

Clique para ver a solução
import React, { useState, useEffect } from 'react';

function RelogioFusoHorario() {
  const fusos = [
    { nome: 'Brasília', offset: -3 },
    { nome: 'Nova York', offset: -5 },
    { nome: 'Londres', offset: 0 },
    { nome: 'Tóquio', offset: 9 },
    { nome: 'Sydney', offset: 11 },
    { nome: 'Dubai', offset: 4 }
  ];

  const [hora, setHora] = useState(new Date());
  const [fusoSelecionado, setFusoSelecionado] = useState(fusos[0]);

  useEffect(() => {
    const timer = setInterval(() => {
      setHora(new Date());
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  const calcularHoraFuso = () => {
    const horaUTC = hora.getTime() + (hora.getTimezoneOffset() * 60000);
    const horaFuso = new Date(horaUTC + (3600000 * fusoSelecionado.offset));
    return horaFuso;
  };

  const horaFormatada = calcularHoraFuso().toLocaleTimeString('pt-BR');
  const dataFormatada = calcularHoraFuso().toLocaleDateString('pt-BR');

  return (
    <div style={{ textAlign: 'center', padding: '20px' }}>
      <h1>Relógio Mundial</h1>
      
      <div style={{ margin: '20px' }}>
        <label htmlFor="fuso">Selecione o fuso horário: </label>
        <select
          id="fuso"
          value={fusoSelecionado.nome}
          onChange={(e) => {
            const fuso = fusos.find(f => f.nome === e.target.value);
            setFusoSelecionado(fuso);
          }}
        >
          {fusos.map(fuso => (
            <option key={fuso.nome} value={fuso.nome}>
              {fuso.nome} (UTC{fuso.offset >= 0 ? '+' : ''}{fuso.offset})
            </option>
          ))}
        </select>
      </div>

      <div style={{ 
        fontSize: '48px', 
        fontFamily: 'monospace',
        margin: '20px',
        padding: '20px',
        border: '2px solid #333',
        borderRadius: '10px',
        backgroundColor: '#f0f0f0'
      }}>
        {horaFormatada}
      </div>

      <div style={{ fontSize: '20px', color: '#666' }}>
        {dataFormatada}
      </div>

      <div style={{ marginTop: '20px', fontSize: '14px', color: '#888' }}>
        <p>Hora local do seu navegador: {new Date().toLocaleTimeString('pt-BR')}</p>
      </div>
    </div>
  );
}

export default RelogioFusoHorario;

Desafio Extra

Adicione as seguintes funcionalidades:

  1. Formato 12/24 horas: Toggle para alternar entre formatos
  2. Múltiplos relógios: Permitir adicionar vários relógios com fusos diferentes
  3. Alarme: Adicionar funcionalidade de alarme para um horário específico
  4. Tema: Alternar entre tema claro e escuro
  5. Persistência: Salvar as preferências no localStorage
3 content items