Ficha 01 · React fundamentos
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.