⚠️ Para aproveitar ao máximo estes módulos, certifica-te de que completaste o curso de JavaScript primeiro.
⚛️ React — Módulos 07–12

Aprende React
moderno com Hooks

Seis módulos progressivos: componentes, props, estado, hooks avançados, formulários e um projeto final completo.

▶ Começar pelo Módulo 07 ← Índice do Curso
⚛️
React · 07
Introdução ao React
O que é React e porquê usá-lo. JSX, componentes funcionais e renderização.
📖 7 lições
✏️ 4 exercícios
7.1 O que é o React e porquê usá-lo? ▾

React é uma biblioteca JavaScript criada pela Meta para construir interfaces de utilizador. A ideia central é dividir a UI em componentes reutilizáveis.

O React usa um Virtual DOM — uma representação em memória do DOM real. Quando o estado muda, o React calcula o mínimo de alterações necessárias e atualiza só o que mudou. Isto torna as aplicações muito eficientes.

React não é um framework completo — é só a camada de "View". Para routing usa-se React Router, para estado global usa-se Zustand/Redux, para estilos usa-se CSS Modules ou Tailwind.
// Sem React — manipulação manual do DOM
const btn = document.createElement("button");
btn.textContent = "Clica aqui";
btn.addEventListener("click", () => {
  document.querySelector("#contador").textContent++;
});

// Com React — declarativo e mais simples
function Contador() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>
    {count}
  </button>;
}
7.2 JSX — JavaScript + HTML ▾

JSX é uma extensão de sintaxe que permite escrever HTML dentro de JavaScript. O Babel transpila JSX para chamadas React.createElement().

// JSX — parece HTML mas é JavaScript
const elemento = <h1 className="titulo">Olá, mundo!</h1>;

// O que o Babel gera por baixo:
const elemento = React.createElement(
  "h1",
  { className: "titulo" },
  "Olá, mundo!"
);

// Regras importantes do JSX:
// 1. class → className
// 2. for → htmlFor (em labels)
// 3. Tem de ter um único elemento raiz
// 4. Expressões JS vão dentro de {}
// 5. Tags auto-fechadas: <img />, <br />

const nome = "Ana";
const ui = (
  <div>
    <h1>Olá, {nome}!</h1>
    <p>{2 + 2} anos de experiência</p>
    <img src="foto.jpg" alt="Foto" />
  </div>
);
7.3 O teu primeiro componente ▾

Componentes são funções JavaScript que retornam JSX. O nome começa sempre com letra maiúscula.

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

// Componente com lógica
function Relogio() {
  const agora = new Date();
  const hora = agora.toLocaleTimeString("pt-PT");

  return (
    <div className="relogio">
      <p>Hora atual:</p>
      <h2>{hora}</h2>
    </div>
  );
}

// Renderizar na página
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(
  document.getElementById("root")
);
root.render(<Relogio />);
7.4 Renderização condicional e listas ▾

React torna a renderização condicional e de listas muito expressiva usando JSX e JavaScript puro.

// Renderização condicional
function Alerta({ tipo, mensagem }) {
  // if/else antes do return
  if (!mensagem) return null; // não renderiza nada

  return (
    <div className={`alerta alerta-${tipo}`}>
      {/* Operador && — renderiza se verdade */}
      {tipo === "erro" && <span>❌</span>}

      {/* Ternário — um ou outro */}
      {tipo === "sucesso"
        ? <span>✅ {mensagem}</span>
        : <span>⚠️ {mensagem}</span>
      }
    </div>
  );
}

// Listas — sempre com key única!
function ListaTarefas({ tarefas }) {
  return (
    <ul>
      {tarefas.map(tarefa => (
        <li key={tarefa.id}>
          {tarefa.texto}
        </li>
      ))}
    </ul>
  );
}
Nunca uses o índice do array como key se a lista pode ser reordenada ou filtrada. Usa sempre um ID único do próprio item.
Exercícios do Módulo 07
Fácil
Componente Cartão de Perfil
Cria um componente CartaoPerfil que recebe nome, cargo e avatar como props e os apresenta num cartão.
function CartaoPerfil({ nome, cargo, avatar }) {
  // O teu JSX aqui
}

// Usar:
<CartaoPerfil nome="Ana Silva" cargo="Dev" avatar="🧑‍💻" />
function CartaoPerfil({ nome, cargo, avatar }) {
  return (
    <div className="cartao">
      <span className="avatar">{avatar}</span>
      <h2>{nome}</h2>
      <p>{cargo}</p>
    </div>
  );
}
Médio
Lista Dinâmica
Cria um componente que recebe um array de strings e os apresenta numa lista <ul>. Não te esqueças da prop key!
function ListaItens({ itens }) {
  // Usa .map() para renderizar <li>
}
<ListaItens itens={["Pão", "Leite", "Ovos"]} />
function ListaItens({ itens }) {
  return (
    <ul>
      {itens.map((item, i) => (
        <li key={i}>{item}</li>
      ))}
    </ul>
  );
}
Próximo módulo
🎁 Props e Composição
→
🎁
React · 08
Props e Composição
Como passar dados entre componentes com props. Composição vs herança. Children.
📖 6 lições
✏️ 5 exercícios
8.1 Props — passar dados para componentes ▾

Props (abreviação de properties) são o mecanismo para passar dados de um componente pai para um componente filho. As props são só de leitura — um filho nunca deve modificar as props recebidas.

// Componente pai passa props
function App() {
  return (
    <div>
      <Botao
        texto="Guardar"
        cor="azul"
        desativado={false}
        aoClicar={() => console.log("clique!")}
      />
    </div>
  );
}

// Componente filho recebe props
function Botao({ texto, cor, desativado, aoClicar }) {
  return (
    <button
      className={`btn btn-${cor}`}
      disabled={desativado}
      onClick={aoClicar}
    >
      {texto}
    </button>
  );
}
8.2 Props com valores por defeito ▾

Podes definir valores por defeito para props usando desestruturação ou defaultProps.

// Valores por defeito na desestruturação (recomendado)
function Avatar({
  nome = "Utilizador",
  src = "/avatar-default.png",
  tamanho = 40
}) {
  return (
    <img
      src={src}
      alt={nome}
      width={tamanho}
      height={tamanho}
      style={{ borderRadius: "50%" }}
    />
  );
}

// Usar sem algumas props
<Avatar nome="Ana" />  // src e tamanho usam defeito
8.3 children — composição de componentes ▾

A prop especial children representa o conteúdo entre as tags de abertura e fecho de um componente. É a base da composição em React.

// Componente "contentor" com children
function Cartao({ titulo, children }) {
  return (
    <div className="cartao">
      {titulo && <h3 className="cartao-titulo">{titulo}</h3>}
      <div className="cartao-corpo">
        {children}
      </div>
    </div>
  );
}

// Usar com qualquer conteúdo
<Cartao titulo="O meu perfil">
  <Avatar nome="Ana" />
  <p>Desenvolvedora Full-Stack</p>
  <Botao texto="Seguir" />
</Cartao>
Prefere sempre composição a herança em React. Em vez de criar uma classe que herda de outra, usa children e props para personalizar componentes.
Exercícios do Módulo 08
Médio
Componente Layout
Cria um componente Layout com um cabeçalho, área de conteúdo (children) e rodapé.
function Layout({ titulo, children }) {
  // Cabeçalho com titulo, secção principal com children, rodapé
}

<Layout titulo="A minha App">
  <p>Conteúdo aqui</p>
</Layout>
function Layout({ titulo, children }) {
  return (
    <div>
      <header><h1>{titulo}</h1></header>
      <main>{children}</main>
      <footer><p>© 2025</p></footer>
    </div>
  );
}
Próximo módulo
🔄 Estado com useState
→
🔄
React · 09
Estado com useState
Gestão de estado local. useState hook, imutabilidade e re-renderização.
📖 8 lições
✏️ 7 exercícios
9.1 useState — o hook de estado ▾

useState é o hook mais fundamental do React. Permite que componentes funcionais guardem e atualizem valores que causam re-renderização.

import { useState } from "react";

function Contador() {
  // [valor atual, função para atualizar]
  const [count, setCount] = useState(0);
  //                                  ^ valor inicial

  return (
    <div>
      <p>Contagem: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        +
      </button>
      <button onClick={() => setCount(count - 1)}>
        -
      </button>
      <button onClick={() => setCount(0)}>
        Reset
      </button>
    </div>
  );
}
9.2 Atualização funcional e imutabilidade ▾

Quando o novo estado depende do anterior, usa a forma funcional do setter. Para objetos e arrays, nunca mutes — cria sempre uma cópia.

// ✅ Forma funcional — usa o valor mais recente
setCount(prev => prev + 1);

// Imutabilidade com objetos
const [user, setUser] = useState({ nome: "Ana", idade: 28 });

// ❌ Errado — mutação direta
user.idade = 29;
setUser(user);

// ✅ Correto — criar nova cópia
setUser(prev => ({ ...prev, idade: 29 }));

// Imutabilidade com arrays
const [lista, setLista] = useState(["a", "b"]);

// Adicionar
setLista(prev => [...prev, "c"]);

// Remover por índice
setLista(prev => prev.filter((_, i) => i !== 1));

// Atualizar um item
setLista(prev => prev.map((item, i) =>
  i === 0 ? "novo" : item
));
9.3 Estado com inputs e formulários ▾

A forma mais comum de usar useState é para controlar o valor de campos de formulário — chamados componentes controlados.

function FormularioNome() {
  const [nome, setNome] = useState("");
  const [enviado, setEnviado] = useState(false);

  function handleSubmit(e) {
    e.preventDefault();
    setEnviado(true);
  }

  if (enviado) {
    return <p>Olá, {nome}! 👋</p>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        O teu nome:
        <input
          type="text"
          value={nome}
          onChange={e => setNome(e.target.value)}
        />
      </label>
      <button type="submit">Enviar</button>
    </form>
  );
}
Exercícios do Módulo 09
Médio
Contador com useState
Cria um componente Contador com botões para incrementar, decrementar e reset. O valor nunca pode ser negativo.
import { useState } from "react";

function Contador() {
  const [count, setCount] = useState(0);
  // Adiciona botões e lógica
}
function Contador() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(c => Math.max(0, c - 1))}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}
Próximo módulo
🪝 Hooks Avançados
→
🪝
React · 10
Hooks Avançados
useEffect, useContext, useReducer, useMemo, useCallback — quando e como usar.
📖 10 lições
✏️ 8 exercícios
10.1 useEffect — efeitos secundários ▾

useEffect serve para executar código após a renderização — chamadas API, subscrições, timers ou qualquer operação que afeta o mundo fora do componente.

import { useState, useEffect } from "react";

function TituloSincrono({ titulo }) {
  // Efeito que sincroniza com o document.title
  useEffect(() => {
    document.title = titulo;

    // Cleanup — executado antes do próximo efeito
    return () => {
      document.title = "App";
    };
  }, [titulo]); // dependências — re-executa quando titulo muda

  return <h1>{titulo}</h1>;
}

// Casos de uso comuns:
// [] — só executa ao montar
useEffect(() => { buscarDados(); }, []);

// [id] — executa quando id muda
useEffect(() => { buscarUtilizador(id); }, [id]);

// sem array — executa em cada render (raro!)
useEffect(() => { console.log("render"); });
10.2 useContext — estado global ▾

useContext permite partilhar dados entre componentes sem ter de passar props por cada nível da árvore — o chamado "prop drilling".

import { createContext, useContext, useState } from "react";

// 1. Criar o context
const TemaContext = createContext("claro");

// 2. Providenciar com o Provider
function App() {
  const [tema, setTema] = useState("claro");
  return (
    <TemaContext.Provider value={{ tema, setTema }}>
      <Pagina />
    </TemaContext.Provider>
  );
}

// 3. Consumir em qualquer componente filho
function BotaoTema() {
  const { tema, setTema } = useContext(TemaContext);
  return (
    <button onClick={() => setTema(t => t === "claro" ? "escuro" : "claro")}>
      Tema atual: {tema}
    </button>
  );
}
10.3 useReducer — estado complexo ▾

useReducer é uma alternativa ao useState para estado mais complexo. Segue o padrão Redux — uma função reducer define como o estado muda.

import { useReducer } from "react";

// Reducer — função pura que retorna novo estado
function reducer(state, action) {
  switch (action.type) {
    case "incrementar":
      return { ...state, count: state.count + 1 };
    case "decrementar":
      return { ...state, count: Math.max(0, state.count - 1) };
    case "reset":
      return { count: 0 };
    default:
      return state;
  }
}

function Contador() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "incrementar" })}>+</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  );
}
10.4 useMemo e useCallback — otimização ▾

useMemo memoriza o resultado de um cálculo pesado. useCallback memoriza uma função. Usa-os só quando há um problema real de desempenho.

import { useMemo, useCallback } from "react";

// useMemo — não recalcula se dependências não mudaram
const totalFiltrado = useMemo(() => {
  console.log("a calcular..."); // só corre quando lista muda
  return lista
    .filter(item => item.ativo)
    .reduce((acc, item) => acc + item.valor, 0);
}, [lista]);

// useCallback — mesma função entre renders
// Útil quando passas funções a componentes memorizados
const handleClick = useCallback(() => {
  setSeleccionado(id);
}, [id]);

// Regra geral: NÃO optimizes prematuramente.
// Só usa useMemo/useCallback quando tens evidência
// de que algo é lento com o React DevTools Profiler.
Exercícios do Módulo 10
Difícil
Fetch com useEffect
Cria um componente que vai buscar dados de uma API quando monta e mostra loading enquanto aguarda.
import { useState, useEffect } from "react";

function DadosAPI() {
  const [dados, setDados] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Vai buscar dados aqui
  }, []);

  if (loading) return <p>A carregar...</p>;
  // Renderiza os dados
}
function DadosAPI() {
  const [dados, setDados] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function buscar() {
      try {
        const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
        const json = await res.json();
        setDados(json);
      } finally {
        setLoading(false);
      }
    }
    buscar();
  }, []);

  if (loading) return <p>A carregar...</p>;
  return <div><h2>{dados?.title}</h2></div>;
}
Próximo módulo
📝 Formulários e API Fetch
→
📝
React · 11
Formulários e API Fetch
Formulários controlados e não controlados. Fetch API com useEffect. Loading e erros.
📖 7 lições
✏️ 6 exercícios
11.1 Formulários controlados ▾

Num componente controlado, o React é a única fonte de verdade para os valores dos inputs. Cada mudança atualiza o estado, e o estado controla o input.

function FormularioContato() {
  const [form, setForm] = useState({
    nome: "", email: "", mensagem: ""
  });
  const [erros, setErros] = useState({});

  // Handler genérico para todos os inputs
  function handleChange(e) {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
  }

  function validar() {
    const errs = {};
    if (!form.nome) errs.nome = "Nome é obrigatório";
    if (!form.email.includes("@")) errs.email = "Email inválido";
    return errs;
  }

  function handleSubmit(e) {
    e.preventDefault();
    const errs = validar();
    if (Object.keys(errs).length > 0) {
      setErros(errs); return;
    }
    console.log("Enviado:", form);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="nome" value={form.nome} onChange={handleChange} />
      {erros.nome && <span className="erro">{erros.nome}</span>}
      <button type="submit">Enviar</button>
    </form>
  );
}
11.2 Fetch API com estados de loading e erro ▾

O padrão mais comum em React é três estados: loading, dados e erro. Gere os três para uma boa experiência de utilizador.

function ListaUtilizadores() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [erro, setErro] = useState(null);

  useEffect(() => {
    async function buscar() {
      try {
        const res = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        );
        if (!res.ok) throw new Error(`Erro ${res.status}`);
        const data = await res.json();
        setUsers(data);
      } catch (e) {
        setErro(e.message);
      } finally {
        setLoading(false);
      }
    }
    buscar();
  }, []);

  if (loading) return <p>⏳ A carregar...</p>;
  if (erro) return <p style={{ color: "red" }}>❌ {erro}</p>;

  return (
    <ul>
      {users.map(u => (
        <li key={u.id}>{u.name} — {u.email}</li>
      ))}
    </ul>
  );
}
11.3 Hook personalizado — useFetch ▾

Quando repetes a mesma lógica em vários componentes, extrai-a para um hook personalizado. Começa sempre com "use".

// 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(() => {
    let cancelado = false; // evita race conditions
    async function buscar() {
      setLoading(true);
      try {
        const res = await fetch(url);
        const json = await res.json();
        if (!cancelado) setData(json);
      } catch (e) {
        if (!cancelado) setError(e.message);
      } finally {
        if (!cancelado) setLoading(false);
      }
    }
    buscar();
    return () => { cancelado = true; }; // cleanup
  }, [url]);

  return { data, loading, error };
}

// Usar em qualquer componente
function PostsList() {
  const { data, loading, error } = useFetch(
    "https://jsonplaceholder.typicode.com/posts"
  );
  if (loading) return <p>A carregar...</p>;
  if (error) return <p>Erro: {error}</p>;
  return <p>{data?.length} posts carregados</p>;
}
Exercícios do Módulo 11
Médio
Formulário de Registo
Cria um formulário com nome, email e password. Valida que a password tem pelo menos 6 caracteres antes de submeter.
function FormRegisto() {
  const [form, setForm] = useState({
    nome: "", email: "", password: ""
  });
  // Implementa validação e submissão
}
function FormRegisto() {
  const [form, setForm] = useState({ nome: "", email: "", password: "" });
  const [erro, setErro] = useState("");

  function handleChange(e) {
    setForm(p => ({ ...p, [e.target.name]: e.target.value }));
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (form.password.length < 6) {
      setErro("Password deve ter pelo menos 6 caracteres");
      return;
    }
    setErro("");
    console.log("Registado:", form);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="nome" placeholder="Nome" value={form.nome} onChange={handleChange} />
      <input name="email" placeholder="Email" value={form.email} onChange={handleChange} />
      <input name="password" type="password" placeholder="Password" value={form.password} onChange={handleChange} />
      {erro && <p style={{ color: "red" }}>{erro}</p>}
      <button type="submit">Registar</button>
    </form>
  );
}
Módulo Final
🚀 Projeto Final: App Completa
→
🚀
React · 12
Projeto Final: App de Tarefas
Construir uma aplicação completa com React: lista de tarefas avançada com filtros, busca e persistência.
📖 Projeto guiado
🏆 Certificado

Neste projeto final vais construir uma aplicação de gestão de tarefas completa, aplicando tudo o que aprendeste nos 11 módulos anteriores. A app incluirá adicionar, remover e filtrar tarefas, com persistência no localStorage.

Passo 1
Estrutura e Componentes
Cria a estrutura do projeto com Vite. Divide em componentes: App, FormularioTarefa, ListaTarefas, ItemTarefa, FiltrosTarefas.
Passo 2
Estado com useReducer
Usa useReducer para gerir o estado das tarefas. Ações: ADICIONAR, REMOVER, TOGGLE_COMPLETA, EDITAR.
Passo 3
Filtros e Busca
Adiciona filtros por estado (todas/ativas/completas) e busca por texto. Usa useMemo para calcular as tarefas filtradas eficientemente.
Passo 4
Persistência com localStorage
Usa useEffect para guardar as tarefas no localStorage sempre que mudam, e para as recuperar ao iniciar a app.
Passo 5
Integração com API
Adiciona a opção de importar tarefas de uma API (jsonplaceholder). Usa o hook useFetch criado no módulo 11.
Código base do projeto
// App.jsx — estrutura principal
import { useReducer, useMemo, useEffect } from "react";

const ACOES = {
  ADICIONAR: "adicionar",
  REMOVER: "remover",
  TOGGLE: "toggle",
};

function reducer(tarefas, acao) {
  switch (acao.type) {
    case ACOES.ADICIONAR:
      return [...tarefas, {
        id: Date.now(), texto: acao.texto, completa: false
      }];
    case ACOES.REMOVER:
      return tarefas.filter(t => t.id !== acao.id);
    case ACOES.TOGGLE:
      return tarefas.map(t =>
        t.id === acao.id ? { ...t, completa: !t.completa } : t
      );
    default: return tarefas;
  }
}

export default function App() {
  const inicial = JSON.parse(
    localStorage.getItem("tarefas") || "[]"
  );

  const [tarefas, dispatch] = useReducer(reducer, inicial);
  const [filtro, setFiltro] = useState("todas");
  const [busca, setBusca] = useState("");

  // Persistência automática
  useEffect(() => {
    localStorage.setItem("tarefas", JSON.stringify(tarefas));
  }, [tarefas]);

  // Filtrar e pesquisar (memoizado)
  const tarefasVisiveis = useMemo(() => {
    return tarefas
      .filter(t => {
        if (filtro === "ativas") return !t.completa;
        if (filtro === "completas") return t.completa;
        return true;
      })
      .filter(t =>
        t.texto.toLowerCase().includes(busca.toLowerCase())
      );
  }, [tarefas, filtro, busca]);

  return (
    <div className="app">
      <h1>📋 As minhas tarefas</h1>

      <FormularioTarefa
        onAdicionar={texto => dispatch({ type: ACOES.ADICIONAR, texto })}
      />

      <FiltrosTarefas
        filtro={filtro}
        onFiltro={setFiltro}
        busca={busca}
        onBusca={setBusca}
      />

      <ListaTarefas
        tarefas={tarefasVisiveis}
        onToggle={id => dispatch({ type: ACOES.TOGGLE, id })}
        onRemover={id => dispatch({ type: ACOES.REMOVER, id })}
      />

      <p>{tarefas.filter(t => !t.completa).length} tarefas por fazer</p>
    </div>
  );
}
🎉 Parabéns — Curso Concluído!
🏆

Completaste o curso de JavaScript + React!

Aprendeste desde variáveis básicas até componentes React com hooks avançados e um projeto completo. O próximo passo é construir algo real!

🧠 Fazer o Quiz Final ← Voltar ao Índice