Partilhar: WhatsApp
aulify
UC UC00621 · T. Desenv. Software

Ficha 01 · React fundamentos

Componentes, props, estado
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

Exercício 1 · Setup e primeiro componente

Cria um novo projecto Vite chamado react-pratica. Substitui o conteúdo de App.jsx por um componente <Cartao /> que recebe nome, cargo e email como props e mostra-os formatados num cartão.

Resposta:
npm create vite@latest react-pratica -- --template react
cd react-pratica
npm install
npm run dev
// src/App.jsx
function Cartao({ nome, cargo, email }) {
  return (
    <div style={{ border: "1px solid #ccc", padding: 20, borderRadius: 8 }}>
      <h2>{nome}</h2>
      <p>{cargo}</p>
      <a href={`mailto:${email}`}>{email}</a>
    </div>
  );
}

function App() {
  return (
    <Cartao 
      nome="Ana Silva" 
      cargo="Frontend Developer" 
      email="ana@exemplo.pt" 
    />
  );
}

export default App;

Exercício 2 · Lista de cartões

Cria um array de 3 utilizadores e renderiza um <Cartao /> para cada um usando .map(). Não esqueças a key.

Resposta:
const utilizadores = [
  { id: 1, nome: "Ana Silva", cargo: "Frontend", email: "ana@x.pt" },
  { id: 2, nome: "Bruno Costa", cargo: "Backend", email: "bruno@x.pt" },
  { id: 3, nome: "Carla Dias", cargo: "Designer", email: "carla@x.pt" },
];

function App() {
  return (
    <div>
      {utilizadores.map(u => (
        <Cartao 
          key={u.id}
          nome={u.nome}
          cargo={u.cargo}
          email={u.email}
        />
      ))}
    </div>
  );
}

Exercício 3 · Contador com useState

Cria um componente <Counter /> com: - Display do número actual (começa em 0). - Botão "+1" para incrementar. - Botão "-1" para decrementar. - Botão "Reset" para voltar a 0.

Resposta:
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

Exercício 4 · Toggle light/dark

Cria um componente <App /> com um botão que alterna entre tema claro e escuro. Quando dark, fundo preto e texto branco; quando light, o inverso.

Resposta:
import { useState } from "react";

function App() {
  const [dark, setDark] = useState(false);

  const estilo = {
    background: dark ? "#000" : "#fff",
    color: dark ? "#fff" : "#000",
    minHeight: "100vh",
    padding: 20,
  };

  return (
    <div style={estilo}>
      <h1>Olá!</h1>
      <p>Tema actual: {dark ? "dark" : "light"}</p>
      <button onClick={() => setDark(!dark)}>
        Mudar para {dark ? "light" : "dark"}
      </button>
    </div>
  );
}

Exercício 5 · Formulário controlado

Cria um formulário de registo com 3 campos: nome, email e idade. Ao submeter (preventDefault), mostra os valores num <pre> abaixo do form.

Resposta:
import { useState } from "react";

function Registo() {
  const [form, setForm] = useState({ nome: "", email: "", idade: "" });
  const [submetido, setSubmetido] = useState(null);

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

  const handleSubmit = (e) => {
    e.preventDefault();
    setSubmetido(form);
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input name="nome" value={form.nome} onChange={handleChange} placeholder="Nome" />
        <input name="email" type="email" value={form.email} onChange={handleChange} placeholder="Email" />
        <input name="idade" type="number" value={form.idade} onChange={handleChange} placeholder="Idade" />
        <button type="submit">Registar</button>
      </form>

      {submetido && (
        <pre>{JSON.stringify(submetido, null, 2)}</pre>
      )}
    </div>
  );
}

Exercício 6 · Lista de tarefas (Todo)

Cria uma aplicação Todo completa: - Input + botão para adicionar tarefa. - Lista de tarefas existentes. - Checkbox para marcar como feita (texto riscado). - Botão para remover cada tarefa.

Resposta:
import { useState } from "react";

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");

  const adicionar = (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    setTodos([...todos, { id: Date.now(), texto: input, feito: false }]);
    setInput("");
  };

  const toggle = (id) => {
    setTodos(todos.map(t => 
      t.id === id ? { ...t, feito: !t.feito } : t
    ));
  };

  const remover = (id) => {
    setTodos(todos.filter(t => t.id !== id));
  };

  return (
    <div>
      <h1>To-do</h1>
      <form onSubmit={adicionar}>
        <input 
          value={input} 
          onChange={(e) => setInput(e.target.value)}
          placeholder="Nova tarefa..."
        />
        <button type="submit">Adicionar</button>
      </form>

      <ul>
        {todos.map(t => (
          <li key={t.id}>
            <input 
              type="checkbox" 
              checked={t.feito} 
              onChange={() => toggle(t.id)} 
            />
            <span style={{ textDecoration: t.feito ? "line-through" : "none" }}>
              {t.texto}
            </span>
            <button onClick={() => remover(t.id)}>X</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Exercício 7 · Estado partilhado (lifting state up)

Cria dois componentes <Termometro />: um para Celsius, outro para Fahrenheit. Quando o user escreve em qualquer um deles, o outro actualiza com o valor convertido. Sobe o estado para o pai.

Fórmulas: - F = C × 9/5 + 32 - C = (F - 32) × 5/9

Resposta:
import { useState } from "react";

function Termometro({ unidade, valor, onChange }) {
  return (
    <div>
      <label>{unidade}: </label>
      <input 
        type="number" 
        value={valor} 
        onChange={(e) => onChange(e.target.value)} 
      />
    </div>
  );
}

function App() {
  const [celsius, setCelsius] = useState("");
  const [fahrenheit, setFahrenheit] = useState("");

  const handleCelsius = (val) => {
    setCelsius(val);
    if (val === "") {
      setFahrenheit("");
    } else {
      const f = (Number(val) * 9/5) + 32;
      setFahrenheit(f.toFixed(2));
    }
  };

  const handleFahrenheit = (val) => {
    setFahrenheit(val);
    if (val === "") {
      setCelsius("");
    } else {
      const c = (Number(val) - 32) * 5/9;
      setCelsius(c.toFixed(2));
    }
  };

  return (
    <div>
      <Termometro unidade="Celsius" valor={celsius} onChange={handleCelsius} />
      <Termometro unidade="Fahrenheit" valor={fahrenheit} onChange={handleFahrenheit} />
    </div>
  );
}

Exercício 8 · Componente Modal reutilizável

Cria um componente <Modal isOpen onClose> que mostra um overlay com children no centro. Tem um botão "X" no topo direito que chama onClose. Usa-o numa app para mostrar/esconder uma mensagem de boas-vindas.

Resposta:
import { useState } from "react";

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return (
    <div 
      style={{
        position: "fixed",
        top: 0, left: 0, right: 0, bottom: 0,
        background: "rgba(0,0,0,0.5)",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
      onClick={onClose}
    >
      <div 
        style={{
          background: "white",
          padding: 30,
          borderRadius: 8,
          minWidth: 300,
          position: "relative",
        }}
        onClick={(e) => e.stopPropagation()}
      >
        <button 
          onClick={onClose}
          style={{
            position: "absolute",
            top: 10, right: 10,
          }}
        >
          X
        </button>
        {children}
      </div>
    </div>
  );
}

function App() {
  const [open, setOpen] = useState(true);

  return (
    <div>
      <button onClick={() => setOpen(true)}>Abrir modal</button>
      <Modal isOpen={open} onClose={() => setOpen(false)}>
        <h2>Bem-vindo!</h2>
        <p>Esta é uma mensagem do modal.</p>
      </Modal>
    </div>
  );
}

Nota: e.stopPropagation() impede que clique dentro do modal feche o overlay; onClick={onClose} no overlay permite fechar clicando fora.