Javascript

Introdução ao React Já leu

15 min de leitura

Introdução ao React
Você passou cinco módulos dominando JavaScript — assincronismo, APIs, Node.js, qualidade de código. Agora chegamos ao React — a biblioteca que transformou a forma como construímos interfaces web e que hoje é a habilidade

Você passou cinco módulos dominando JavaScript — assincronismo, APIs, Node.js, qualidade de código. Agora chegamos ao React — a biblioteca que transformou a forma como construímos interfaces web e que hoje é a habilidade front-end mais demandada do mercado.

React não é um framework completo como Angular. É uma biblioteca focada em uma coisa só: construir interfaces a partir de componentes. Simples na ideia, poderoso na prática.


O problema que o React resolve

Antes do React, interfaces dinâmicas eram construídas manipulando o DOM manualmente:

// ── Jeito antigo — jQuery / DOM puro ──────────────
let contador = 0;

function incrementar() {
  contador++;
  document.querySelector('#valor').textContent = contador;
  document.querySelector('#btn-decrement').disabled = contador <= 0;

  if (contador > 10) {
    document.querySelector('#aviso').style.display = 'block';
    document.querySelector('#aviso').textContent = 'Limite atingido!';
  } else {
    document.querySelector('#aviso').style.display = 'none';
  }
}

// Para cada mudança de estado:
// 1. Calcular o que mudou
// 2. Encontrar o elemento no DOM
// 3. Atualizar manualmente
// 4. Lembrar de atualizar TUDO que depende daquele estado
// 5. Não esquecer nenhum caso especial

// ── React — descrevendo o resultado desejado ───────
function Contador() {
  const [contador, setContador] = useState(0);

  return (
    <div>
      <p>Valor: {contador}</p>
      <button onClick={() => setContador(c => c + 1)}>+</button>
      <button onClick={() => setContador(c => c - 1)} disabled={contador <= 0}>-</button>
      {contador > 10 && <p>Limite atingido!</p>}
    </div>
  );
}
// React cuida de atualizar o DOM automaticamente
// Você apenas descreve como a UI deve parecer dado um estado

A diferença fundamental: com React você descreve o que deve aparecer, não como atualizar o DOM.


Instalando e criando um projeto

# Vite — a forma moderna e rápida (recomendado)
npm create vite@latest meu-app -- --template react
cd meu-app
npm install
npm run dev

# Alternativa — Create React App (mais pesado, menos atual)
npx create-react-app meu-app
cd meu-app
npm start

A estrutura gerada com Vite:

meu-app/
├── public/
│   └── vite.svg
├── src/
│   ├── assets/
│   ├── App.css
│   ├── App.jsx
│   ├── index.css
│   └── main.jsx      ← ponto de entrada
├── index.html
├── package.json
└── vite.config.js
// src/main.jsx — ponto de entrada
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

JSX — JavaScript + XML

JSX é uma extensão de sintaxe que permite escrever HTML dentro do JavaScript. O Babel (ou Vite) o transforma em chamadas JavaScript:

// JSX que você escreve
const elemento = <h1 className="titulo">Olá, {nome}!</h1>;

// JavaScript que é gerado
const elemento = React.createElement(
  'h1',
  { className: 'titulo' },
  'Olá, ',
  nome,
  '!'
);

Regras importantes do JSX:

// ✅ 1. Sempre um elemento raiz (ou Fragment)
return (
  <div>
    <h1>Título</h1>
    <p>Parágrafo</p>
  </div>
);

// ✅ Fragment — sem elemento extra no DOM
return (
  <>
    <h1>Título</h1>
    <p>Parágrafo</p>
  </>
);

// ✅ 2. Expressões JavaScript entre chaves
const nome = 'Ana';
return <p>Olá, {nome}!</p>;
return <p>Resultado: {2 + 2}</p>;
return <p>{condicao ? 'Sim' : 'Não'}</p>;

// ✅ 3. className em vez de class
return <div className="container active"></div>;

// ✅ 4. htmlFor em vez de for
return <label htmlFor="email">Email:</label>;

// ✅ 5. Estilos como objeto JavaScript
return <div style={{ color: 'red', fontSize: '16px' }}></div>;

// ✅ 6. Tags auto-fechantes
return <input type="text" />;
return <img src="foto.jpg" alt="foto" />;
return <br />;

// ✅ 7. Comentários dentro do JSX
return (
  <div>
    {/* Isso é um comentário JSX */}
    <p>Conteúdo</p>
  </div>
);

Componentes — os blocos de construção

Um componente React é uma função que recebe props e retorna JSX:

// Componente simples
function Saudacao() {
  return <h1>Olá, mundo!</h1>;
}

// Componente com props
function CartaoUsuario({ nome, email, avatar, ativo }) {
  return (
    <div className={`cartao ${ativo ? 'ativo' : 'inativo'}`}>
      <img src={avatar} alt={nome} />
      <h2>{nome}</h2>
      <p>{email}</p>
      <span>{ativo ? '✅ Ativo' : '❌ Inativo'}</span>
    </div>
  );
}

// Usando o componente
function App() {
  return (
    <div>
      <CartaoUsuario
        nome="Ana Paula"
        email="ana@email.com"
        avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Ana"
        ativo={true}
      />
      <CartaoUsuario
        nome="Carlos Silva"
        email="carlos@email.com"
        avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Carlos"
        ativo={false}
      />
    </div>
  );
}

Props — passando dados para componentes

// Props básicas
function Botao({ texto, tipo = 'button', desabilitado = false, onClick }) {
  return (
    <button
      type={tipo}
      disabled={desabilitado}
      onClick={onClick}
      className="btn"
    >
      {texto}
    </button>
  );
}

// Props com children — conteúdo entre as tags
function Card({ titulo, children, rodape }) {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{titulo}</h3>
      </div>
      <div className="card-body">
        {children}
      </div>
      {rodape && (
        <div className="card-footer">
          {rodape}
        </div>
      )}
    </div>
  );
}

// Usando children
function App() {
  return (
    <Card
      titulo="Informações do usuário"
      rodape={<small>Última atualização: hoje</small>}
    >
      <p>Nome: Ana Paula</p>
      <p>Email: ana@email.com</p>
    </Card>
  );
}

// Spread de props — útil para passar muitas props
function EntradaTexto({ label, erro, ...inputProps }) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />  {/* passa type, value, onChange, etc. */}
      {erro && <span className="erro">{erro}</span>}
    </div>
  );
}

<EntradaTexto
  label="Email"
  type="email"
  value={email}
  onChange={e => setEmail(e.target.value)}
  placeholder="seu@email.com"
  erro={erroEmail}
/>

useState — gerenciando estado

import { useState } from 'react';

// Estado simples
function Contador() {
  const [contador, setContador] = useState(0);

  return (
    <div>
      <p>Contagem: {contador}</p>
      <button onClick={() => setContador(contador + 1)}>+1</button>
      <button onClick={() => setContador(0)}>Resetar</button>
    </div>
  );
}

// Estado com objeto
function Formulario() {
  const [formulario, setFormulario] = useState({
    nome: '',
    email: '',
    senha: '',
  });

  // Atualiza apenas o campo alterado — spread + campo dinâmico
  function handleChange(e) {
    const { name, value } = e.target;
    setFormulario(prev => ({ ...prev, [name]: value }));
  }

  return (
    <form>
      <input name="nome" value={formulario.nome} onChange={handleChange} />
      <input name="email" value={formulario.email} onChange={handleChange} />
      <input name="senha" type="password" value={formulario.senha} onChange={handleChange} />
    </form>
  );
}

// Estado com array
function ListaTarefas() {
  const [tarefas, setTarefas] = useState([
    { id: 1, texto: 'Estudar React', feita: false },
    { id: 2, texto: 'Fazer exercícios', feita: true },
  ]);

  function adicionarTarefa(texto) {
    // NUNCA mutate o estado diretamente — sempre cria novo array
    setTarefas(prev => [
      ...prev,
      { id: Date.now(), texto, feita: false },
    ]);
  }

  function alternarTarefa(id) {
    setTarefas(prev =>
      prev.map(t => t.id === id ? { ...t, feita: !t.feita } : t)
    );
  }

  function removerTarefa(id) {
    setTarefas(prev => prev.filter(t => t.id !== id));
  }

  return (
    <ul>
      {tarefas.map(tarefa => (
        <li key={tarefa.id}>
          <input
            type="checkbox"
            checked={tarefa.feita}
            onChange={() => alternarTarefa(tarefa.id)}
          />
          <span style={{ textDecoration: tarefa.feita ? 'line-through' : 'none' }}>
            {tarefa.texto}
          </span>
          <button onClick={() => removerTarefa(tarefa.id)}>🗑</button>
        </li>
      ))}
    </ul>
  );
}

A prop key — por que é obrigatória em listas

// ❌ Sem key — React não sabe o que mudou
{itens.map(item => <Item item={item} />)}

// ❌ Key com índice — problemático quando a ordem muda
{itens.map((item, i) => <Item key={i} item={item} />)}

// ✅ Key com ID único e estável
{itens.map(item => <Item key={item.id} item={item} />)}

// A key ajuda o React a identificar quais itens
// foram adicionados, removidos ou reordenados
// Sem ela, o React re-renderiza tudo desnecessariamente
// ou piora: confunde elementos e perde estado

useEffect — efeitos colaterais

import { useState, useEffect } from 'react';

// Buscar dados ao montar o componente
function ListaUsuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [carregando, setCarregando] = useState(true);
  const [erro, setErro] = useState(null);

  useEffect(() => {
    // Executa após a renderização

    let cancelado = false; // evita atualizar estado após desmontagem

    async function buscar() {
      try {
        setCarregando(true);
        const res = await fetch('https://jsonplaceholder.typicode.com/users');
        const dados = await res.json();
        if (!cancelado) setUsuarios(dados);
      } catch (e) {
        if (!cancelado) setErro(e.message);
      } finally {
        if (!cancelado) setCarregando(false);
      }
    }

    buscar();

    // Cleanup — executado quando o componente desmonta
    return () => { cancelado = true; };

  }, []); // [] = executa apenas uma vez (ao montar)

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

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

// Dependências do useEffect
function BuscaPorTermo({ termo }) {
  const [resultados, setResultados] = useState([]);

  useEffect(() => {
    if (!termo.trim()) { setResultados([]); return; }

    const controller = new AbortController();

    fetch(`/api/busca?q=${termo}`, { signal: controller.signal })
      .then(r => r.json())
      .then(setResultados)
      .catch(e => { if (e.name !== 'AbortError') console.error(e); });

    return () => controller.abort(); // cancela requisição anterior

  }, [termo]); // re-executa toda vez que 'termo' muda

  return <ul>{resultados.map(r => <li key={r.id}>{r.nome}</li>)}</ul>;
}

// Variações do array de dependências
useEffect(() => { /* executa em toda renderização */ });
useEffect(() => { /* executa apenas ao montar */ }, []);
useEffect(() => { /* executa quando a ou b mudam */ }, [a, b]);

Renderização condicional

function StatusPedido({ status, usuario }) {
  // if/return antecipado (early return)
  if (!usuario) return <p>Faça login para ver o pedido.</p>;

  return (
    <div>
      {/* Operador ternário */}
      {status === 'aprovado'
        ? <Aprovado />
        : <Pendente />
      }

      {/* && — renderiza apenas se verdadeiro */}
      {status === 'cancelado' && (
        <div className="alerta-vermelho">
          Pedido cancelado. Entre em contato.
        </div>
      )}

      {/* Múltiplas condições com switch */}
      {(() => {
        switch (status) {
          case 'pendente': return <span>⏳ Aguardando pagamento</span>;
          case 'aprovado': return <span>✅ Aprovado</span>;
          case 'enviado': return <span>🚚 A caminho</span>;
          case 'entregue': return <span>📦 Entregue</span>;
          default: return <span>❓ Status desconhecido</span>;
        }
      })()}

      {/* Nulo retorna nada */}
      {usuario.admin && <BotaoAdmin />}
    </div>
  );
}

Exemplo completo — App de Tarefas

// src/App.jsx
import { useState, useEffect } from 'react';
import './App.css';

// ── Componentes ────────────────────────────────────

function Cabecalho({ total, concluidas }) {
  return (
    <header className="cabecalho">
      <h1>📋 Minhas Tarefas</h1>
      <p>{concluidas} de {total} concluídas</p>
      <div className="barra-progresso">
        <div
          className="barra-fill"
          style={{ width: total > 0 ? `${(concluidas / total) * 100}%` : '0%' }}
        />
      </div>
    </header>
  );
}

function FormularioNovaTarefa({ aoAdicionar }) {
  const [texto, setTexto] = useState('');
  const [prioridade, setPrioridade] = useState('media');

  function handleSubmit(e) {
    e.preventDefault();
    if (!texto.trim()) return;
    aoAdicionar(texto.trim(), prioridade);
    setTexto('');
    setPrioridade('media');
  }

  return (
    <form onSubmit={handleSubmit} className="form-nova-tarefa">
      <input
        type="text"
        value={texto}
        onChange={e => setTexto(e.target.value)}
        placeholder="Nova tarefa..."
        className="input-tarefa"
      />
      <select
        value={prioridade}
        onChange={e => setPrioridade(e.target.value)}
        className="select-prioridade"
      >
        <option value="baixa">🟢 Baixa</option>
        <option value="media">🟡 Média</option>
        <option value="alta">🔴 Alta</option>
      </select>
      <button type="submit" disabled={!texto.trim()}>
        Adicionar
      </button>
    </form>
  );
}

function ItemTarefa({ tarefa, aoAlternar, aoRemover }) {
  const icones = { baixa: '🟢', media: '🟡', alta: '🔴' };

  return (
    <li className={`tarefa-item ${tarefa.feita ? 'feita' : ''} prioridade-${tarefa.prioridade}`}>
      <input
        type="checkbox"
        checked={tarefa.feita}
        onChange={() => aoAlternar(tarefa.id)}
      />
      <span className="tarefa-texto">{tarefa.texto}</span>
      <span className="tarefa-prioridade" title={tarefa.prioridade}>
        {icones[tarefa.prioridade]}
      </span>
      <span className="tarefa-data">
        {new Date(tarefa.criadaEm).toLocaleDateString('pt-BR')}
      </span>
      <button
        onClick={() => aoRemover(tarefa.id)}
        className="btn-remover"
        aria-label="Remover tarefa"
      >
        🗑
      </button>
    </li>
  );
}

function FiltroBotoes({ filtroAtivo, aoFiltrar, tarefas }) {
  const botoes = [
    { valor: 'todas', rotulo: 'Todas', count: tarefas.length },
    { valor: 'pendentes', rotulo: 'Pendentes', count: tarefas.filter(t => !t.feita).length },
    { valor: 'concluidas', rotulo: 'Concluídas', count: tarefas.filter(t => t.feita).length },
  ];

  return (
    <div className="filtros">
      {botoes.map(b => (
        <button
          key={b.valor}
          className={`btn-filtro ${filtroAtivo === b.valor ? 'ativo' : ''}`}
          onClick={() => aoFiltrar(b.valor)}
        >
          {b.rotulo} <span className="badge">{b.count}</span>
        </button>
      ))}
    </div>
  );
}

// ── App principal ──────────────────────────────────

const CHAVE_STORAGE = 'tarefas-react';

function carregarTarefas() {
  try {
    const salvas = localStorage.getItem(CHAVE_STORAGE);
    return salvas ? JSON.parse(salvas) : [];
  } catch {
    return [];
  }
}

export default function App() {
  const [tarefas, setTarefas] = useState(carregarTarefas);
  const [filtro, setFiltro] = useState('todas');

  // Persiste tarefas no localStorage quando mudam
  useEffect(() => {
    localStorage.setItem(CHAVE_STORAGE, JSON.stringify(tarefas));
  }, [tarefas]);

  function adicionarTarefa(texto, prioridade) {
    setTarefas(prev => [
      {
        id: crypto.randomUUID(),
        texto,
        prioridade,
        feita: false,
        criadaEm: new Date().toISOString(),
      },
      ...prev,
    ]);
  }

  function alternarTarefa(id) {
    setTarefas(prev =>
      prev.map(t => t.id === id ? { ...t, feita: !t.feita } : t)
    );
  }

  function removerTarefa(id) {
    setTarefas(prev => prev.filter(t => t.id !== id));
  }

  function limparConcluidas() {
    setTarefas(prev => prev.filter(t => !t.feita));
  }

  // Filtragem
  const tarefasFiltradas = tarefas.filter(t => {
    if (filtro === 'pendentes') return !t.feita;
    if (filtro === 'concluidas') return t.feita;
    return true;
  });

  // Ordenação: alta → media → baixa → concluídas por último
  const prioridadeOrdem = { alta: 0, media: 1, baixa: 2 };
  const tarefasOrdenadas = [...tarefasFiltradas].sort((a, b) => {
    if (a.feita !== b.feita) return a.feita ? 1 : -1;
    return (prioridadeOrdem[a.prioridade] ?? 2) - (prioridadeOrdem[b.prioridade] ?? 2);
  });

  const concluidas = tarefas.filter(t => t.feita).length;

  return (
    <div className="app">
      <Cabecalho total={tarefas.length} concluidas={concluidas} />

      <main className="conteudo">
        <FormularioNovaTarefa aoAdicionar={adicionarTarefa} />

        <FiltroBotoes
          filtroAtivo={filtro}
          aoFiltrar={setFiltro}
          tarefas={tarefas}
        />

        {tarefasOrdenadas.length === 0 ? (
          <p className="lista-vazia">
            {filtro === 'todas'
              ? '🎉 Nenhuma tarefa ainda. Adicione uma!'
              : `Nenhuma tarefa ${filtro === 'pendentes' ? 'pendente' : 'concluída'}.`
            }
          </p>
        ) : (
          <ul className="lista-tarefas">
            {tarefasOrdenadas.map(tarefa => (
              <ItemTarefa
                key={tarefa.id}
                tarefa={tarefa}
                aoAlternar={alternarTarefa}
                aoRemover={removerTarefa}
              />
            ))}
          </ul>
        )}

        {concluidas > 0 && (
          <button
            onClick={limparConcluidas}
            className="btn-limpar"
          >
            🧹 Limpar {concluidas} concluída{concluidas !== 1 ? 's' : ''}
          </button>
        )}
      </main>
    </div>
  );
}

Os conceitos fundamentais que você aprendeu

┌───────────────────────────────────────────────────────┐
│                REACT — CONCEITOS BASE                 │
│                                                       │
│  Componente  → função que retorna JSX                 │
│  Props       → dados passados do pai para o filho     │
│  Estado      → dados que quando mudam, re-renderizam  │
│  JSX         → HTML + JavaScript combinados           │
│  Key         → identifica itens em listas             │
│  useEffect   → efeitos colaterais e ciclo de vida     │
│                                                       │
│  Fluxo de dados:                                      │
│  Pai → (props) → Filho                                │
│  Filho → (callbacks) → Pai (lifting state up)         │
└───────────────────────────────────────────────────────┘

Tarefa para você

Expanda o App de Tarefas com:

// 1. Campo de busca que filtra tarefas em tempo real
//    (use debounce de 300ms com useState + useEffect)

// 2. Edição inline — ao clicar no texto, vira um input
//    Salva ao pressionar Enter ou ao tirar o foco (onBlur)
//    Cancela ao pressionar Escape

// 3. Drag and drop simples — reordenação manual
//    Dica: atributos draggable, onDragStart, onDragOver, onDrop

// 4. Estatísticas expandidas:
//    - Tempo médio de conclusão (criadaEm → data de conclusão)
//    - Tarefa mais antiga ainda pendente
//    - Porcentagem por prioridade

// 5. Temas — botão que alterna entre claro e escuro
//    Persista a preferência no localStorage
//    Use className no elemento raiz: "app tema-claro" | "app tema-escuro"

Conclusão

Neste artigo você aprendeu:

  • O problema que o React resolve e por que ele mudou o front-end
  • Como criar um projeto React com Vite
  • JSX — suas regras e como funciona por baixo
  • Componentes — funções que retornam JSX
  • Props — como passar dados de pai para filho
  • useState — gerenciando estado local com arrays e objetos
  • A prop key e por que é crucial em listas
  • useEffect — buscando dados, limpando efeitos e dependências
  • Renderização condicional — ternário, && e early return
  • Como compor um App completo com múltiplos componentes

No próximo artigo vamos aprofundar os Hooks do ReactuseContext, useReducer, useMemo, useCallback e hooks customizados que elevam suas aplicações ao próximo nível.


📌 Próximo artigo: Aula 39 — React Hooks em profundidade


📚 Fontes e Referências

  • React — Documentação oficial: https://react.dev
  • React — Aprenda React (tutorial oficial): https://react.dev/learn
  • Vite — Documentação: https://vitejs.dev
  • React — Thinking in React: https://react.dev/learn/thinking-in-react
  • React — Describing the UI: https://react.dev/learn/describing-the-ui
  • JavaScript.info — React: https://javascript.info
  • Road to React — Robin Wieruch: https://www.roadtoreact.com
  • Learning React — Alex Banks e Eve Porcello (O'Reilly)
  • roadmap.sh — React: https://roadmap.sh/react
Comentários

Mais em Javascript

MongoDB e Mongoose: banco de dados com Node
MongoDB e Mongoose: banco de dados com Node

Até agora nossa API guardava dados em um array na memória — tudo sumia quando...

Escopo, Hoisting e Closures
Escopo, Hoisting e Closures

Este &eacute; um dos artigos mais importantes da s&eacute;rie. N&atilde;o por...

Introdução ao Node.js: JavaScript fora do navegador
Introdução ao Node.js: JavaScript fora do navegador

Até agora todo o JavaScript que escrevemos rodou no navegador. O navegador é...