Fundamentos de React: Componentes, Dados e APIs
Guia operacional para props, listas, estado, agrupamentos e consumo de APIs
As questões desta página treinam uma competência central de front-end: transformar requisito em interface funcional com explicação técnica clara. O desafio não é decorar sintaxe; é modelar dados, compor componente e validar comportamento na tela.
O percurso mistura quatro frentes complementares: componentização com props, transformação de listas (incluindo agrupamento e ordenação), interação com estado local e integração com APIs externas reais.
Quem fecha os 16 exercícios com entendimento real demonstra domínio inicial consistente de React aplicado a problemas práticos, incluindo fluxo de dados, renderização previsível e leitura correta de payloads.
- Componentes como contratos de entrada e saída.
- Listas com
map, agrupamento comreducee ordenação com critério. - Estado local com
useStatepara inputs, seleções e resultados. - Fetch com tratamento de resposta e exibição legível.
- Separação entre lógica de dados e composição visual.
// App.jsx (modelo de execução)
import Ex01 from "./Ex01";
// import Ex02 from "./Ex02";
// ...
export default function App() {
return <Ex01 />;
}function Saudacao({ nome }) {
return (
<div className="card">
<h2>Olá, {nome}!</h2>
<p>Seja bem-vindo(a) ao React.</p>
</div>
);
}
export default function Ex01() {
return (
<div className="page">
<Saudacao nome="Ana" />
<Saudacao nome="Bruno" />
<Saudacao nome="Carla" />
</div>
);
}O que a questão pede
props e renderizar três instâncias com valores diferentes.Conceito cobrado
A entrada do componente é simples: { nome }. A saída é sempre o mesmo card, mudando apenas o valor recebido.
Como pensar
Como validar
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
function SaudacaoNascimento({ nome, dia, mes, ano }) {
const bissexto = isLeapYear(ano);
return (
<div className="card">
<h2>Olá, {nome}!</h2>
<p>Nascimento: {String(dia).padStart(2, "0")}/{String(mes).padStart(2, "0")}/{ano}</p>
<p>Ano: <strong style={{ color: bissexto ? "green" : "#444" }}>{ano}</strong>{bissexto && " (bissexto)"}</p>
</div>
);
}
export default function Ex02() {
return (
<div className="page">
<SaudacaoNascimento nome="Ana" dia={4} mes={3} ano={2000} />
<SaudacaoNascimento nome="Bruno" dia={12} mes={7} ano={1999} />
<SaudacaoNascimento nome="Carla" dia={29} mes={2} ano={2024} />
</div>
);
}O que a questão pede
Conceito cobrado
As entradas são { nome, dia, mes, ano }. O valor bissexto não vem de fora; ele é derivado de ano.
Como pensar
Como validar
function AvaliacaoFilme({ titulo, totalEstrelas, avaliacao }) {
return (
<div className="card">
{Array.from({ length: totalEstrelas }, (_, i) => (
<span key={i} style={{ color: i < avaliacao ? "red" : "#bbb", fontSize: 24 }}>★</span>
))}
<strong style={{ marginLeft: 12 }}>{titulo}</strong>
</div>
);
}
const filmes = [
{ titulo: "Matrix", totalEstrelas: 5, avaliacao: 5 },
{ titulo: "Interestelar", totalEstrelas: 5, avaliacao: 4 },
{ titulo: "Arrival", totalEstrelas: 5, avaliacao: 4 },
{ titulo: "Blade Runner 2049", totalEstrelas: 5, avaliacao: 5 },
{ titulo: "Duna", totalEstrelas: 5, avaliacao: 4 }
];
export default function Ex03() {
return (
<div className="page">
{filmes.map((filme) => (
<AvaliacaoFilme key={filme.titulo} {...filme} />
))}
</div>
);
}O que a questão pede
Conceito cobrado
Array.from.Cada filme segue { titulo, totalEstrelas, avaliacao }. O número de estrelas ativas é calculado comparando o índice com avaliacao.
Como pensar
Como validar
function AvaliacaoFilme({ titulo, totalEstrelas, avaliacao, cor = "red" }) {
return (
<div className="card">
{Array.from({ length: totalEstrelas }, (_, i) => (
<span key={i} style={{ color: i < avaliacao ? cor : "#bbb", fontSize: 24 }}>★</span>
))}
<strong style={{ marginLeft: 12 }}>{titulo}</strong>
</div>
);
}
export default function Ex04() {
return (
<div className="page">
<AvaliacaoFilme titulo="Matrix" totalEstrelas={5} avaliacao={5} cor="crimson" />
<AvaliacaoFilme titulo="Interestelar" totalEstrelas={5} avaliacao={4} cor="goldenrod" />
<AvaliacaoFilme titulo="Arrival" totalEstrelas={5} avaliacao={4} cor="deepskyblue" />
</div>
);
}O que a questão pede
Conceito cobrado
O contrato passa a ser { titulo, totalEstrelas, avaliacao, cor }. A prop cor altera apenas o estilo das estrelas ativas.
Como pensar
Como validar
import { faker } from "@faker-js/faker";
const funcionarios = Array.from({ length: 10 }, () => ({
id: faker.string.uuid(),
nome: faker.person.fullName(),
cargo: faker.person.jobTitle(),
departamento: faker.commerce.department()
}));
export default function Ex05() {
return (
<ul>
{funcionarios.map((f) => (
<li key={f.id}>
{f.nome} - {f.cargo} ({f.departamento})
</li>
))}
</ul>
);
}O que a questão pede
Conceito cobrado
key estável para cada item.Cada item segue { id, nome, cargo, departamento }. O campo id é usado na key e não deve ser trocado por índice.
Como pensar
map.Como validar
key={index} e perder estabilidade de renderização quando a lista muda.import { faker } from "@faker-js/faker";
const funcionarios = Array.from({ length: 30 }, () => ({
id: faker.string.uuid(),
nome: faker.person.fullName(),
departamento: faker.commerce.department()
}));
const grupos = funcionarios.reduce((acc, f) => {
if (!acc[f.departamento]) acc[f.departamento] = [];
acc[f.departamento].push(f);
return acc;
}, {});
export default function Ex06() {
return (
<div>
{Object.entries(grupos).map(([dep, lista]) => (
<section key={dep} className="card">
<h3>{dep}</h3>
<ul>{lista.map((f) => <li key={f.id}>{f.nome}</li>)}</ul>
</section>
))}
</div>
);
}O que a questão pede
Conceito cobrado
reduce para construir objeto de agrupamento e Object.entries para renderizar.O acumulador vira um objeto como { "TI": [...], "Vendas": [...] }. Cada chave é um departamento e cada valor é um array de funcionários.
Como pensar
Como validar
import { faker } from "@faker-js/faker";
const produtos = Array.from({ length: 50 }, () => ({
id: faker.string.uuid(),
nome: faker.commerce.productName(),
descricao: faker.commerce.productDescription(),
adjetivo: faker.commerce.productAdjective(),
preco: faker.commerce.price({ min: 10, max: 900 })
}));
export default function Ex07() {
return (
<div className="grid">
{produtos.map((p) => (
<article key={p.id} className="card">
<h3>{p.nome}</h3>
<p>{p.descricao}</p>
<strong>{p.adjetivo}</strong>
<p>R$ {p.preco}</p>
</article>
))}
</div>
);
}O que a questão pede
Conceito cobrado
Cada produto segue { id, nome, descricao, adjetivo, preco }. O preço do Faker vem como string e já pode ser exibido.
Como pensar
Como validar
preco pode vir como string e tentar fazer conta sem converter.import { faker } from "@faker-js/faker";
const produtos = Array.from({ length: 50 }, () => ({
id: faker.string.uuid(),
nome: faker.commerce.productName(),
preco: faker.commerce.price({ min: 10, max: 900 }),
departamento: faker.commerce.department()
}));
const porDepartamento = produtos.reduce((acc, p) => {
if (!acc[p.departamento]) acc[p.departamento] = [];
acc[p.departamento].push(p);
return acc;
}, {});
export default function Ex08() {
return (
<div>
{Object.entries(porDepartamento).map(([dep, itens]) => (
<section key={dep} className="card">
<h3>{dep}</h3>
{itens.map((p) => <p key={p.id}>{p.nome} - R$ {p.preco}</p>)}
</section>
))}
</div>
);
}O que a questão pede
Conceito cobrado
Entrada: array de produtos. Saída: objeto indexado por departamento com arrays internos de itens.
Como pensar
reduce e trate o resultado como dado derivado da lista base.Como validar
const [cep, setCep] = useState("");
const [endereco, setEndereco] = useState(null);
const [erro, setErro] = useState("");
async function buscarCep() {
const limpo = cep.replace(/\D/g, "");
if (limpo.length !== 8) {
setEndereco(null);
setErro("Informe um CEP com 8 dígitos.");
return;
}
const res = await fetch(`https://viacep.com.br/ws/${limpo}/json/`);
const data = await res.json();
if (data.erro) {
setEndereco(null);
setErro("CEP não encontrado.");
return;
}
setErro("");
setEndereco(data);
}
<input value={cep} onChange={(e) => setCep(e.target.value)} placeholder="Digite o CEP" />
<button onClick={buscarCep}>Buscar</button>
{erro && <p>{erro}</p>}
{endereco && <p>{endereco.logradouro}, {endereco.bairro} - {endereco.localidade}/{endereco.uf}</p>}O que a questão pede
Conceito cobrado
O ViaCEP retorna campos como logradouro, bairro, localidade e uf; em erro, retorna erro: true.
Como pensar
Como validar
const [ano, setAno] = useState("");
const [ranking, setRanking] = useState([]);
const [erro, setErro] = useState("");
async function buscarRanking() {
const anoNum = Number(ano);
if (!Number.isFinite(anoNum)) {
setErro("Informe um ano numérico.");
setRanking([]);
return;
}
const decada = Math.floor(anoNum / 10) * 10;
const url = `https://servicodados.ibge.gov.br/api/v2/censos/nomes/ranking/?decada=${decada}`;
const res = await fetch(url);
const data = await res.json();
const lista = data[0]?.res || [];
setErro("");
setRanking(lista.slice(0, 10));
}
<input value={ano} onChange={(e) => setAno(e.target.value)} placeholder="Ano" />
<button onClick={buscarRanking}>Consultar</button>
{erro && <p>{erro}</p>}
<ol>{ranking.map((item) => <li key={item.nome}>{item.nome} ({item.frequencia})</li>)}</ol>O que a questão pede
Conceito cobrado
O retorno vem como array e os nomes ficam em data[0].res, cada item contendo nome e frequencia.
Como pensar
Como validar
const [ufs, setUfs] = useState([]);
const [carregando, setCarregando] = useState(false);
async function carregarUFs() {
setCarregando(true);
const res = await fetch("https://servicodados.ibge.gov.br/api/v1/localidades/estados?orderBy=nome");
const data = await res.json();
setUfs(data);
setCarregando(false);
}
<button onClick={carregarUFs} disabled={carregando}>
{carregando ? "Carregando..." : "Carregar UFs"}
</button>
<select>
<option value="">Selecione</option>
{ufs.map((uf) => (
<option key={uf.id} value={uf.sigla}>
{uf.sigla} - {uf.nome}
</option>
))}
</select>O que a questão pede
Conceito cobrado
Cada UF retorna no formato { id, sigla, nome }. Use id como chave da opção.
Como pensar
Como validar
const [uf, setUf] = useState("");
const [municipios, setMunicipios] = useState([]);
useEffect(() => {
if (!uf) {
setMunicipios([]);
return;
}
fetch(`https://servicodados.ibge.gov.br/api/v1/localidades/estados/${uf}/municipios`)
.then((r) => r.json())
.then((lista) => setMunicipios(lista));
}, [uf]);
<select value={uf} onChange={(e) => setUf(e.target.value)}>
<option value="">Selecione UF</option>
{ufs.map((item) => <option key={item.id} value={item.sigla}>{item.sigla}</option>)}
</select>
<ul>{municipios.slice(0, 20).map((m) => <li key={m.id}>{m.nome}</li>)}</ul>O que a questão pede
Conceito cobrado
useEffect e atualização reativa da interface.A API devolve array de municípios com id e nome. Esse array deve ser substituído a cada troca de UF.
Como pensar
Como validar
uf na lista de dependências do useEffect e travar atualização.const [paises, setPaises] = useState([]);
async function carregarPaises() {
const res = await fetch("https://restcountries.com/v3.1/all?fields=name,cca3,flags");
const data = await res.json();
const ordenados = data.sort((a, b) => a.name.common.localeCompare(b.name.common));
setPaises(ordenados);
}
<button onClick={carregarPaises}>Carregar países</button>
<ul>
{paises.slice(0, 30).map((p) => (
<li key={p.cca3}>{p.name.common}</li>
))}
</ul>O que a questão pede
Conceito cobrado
Use apenas os campos necessários: name, cca3 e flags. No endpoint /all, a documentação atual exige ?fields=....
Como pensar
Como validar
cca3.const [paisSelecionado, setPaisSelecionado] = useState("");
const pais = paises.find((p) => p.cca3 === paisSelecionado);
<select value={paisSelecionado} onChange={(e) => setPaisSelecionado(e.target.value)}>
<option value="">Selecione</option>
{paises.map((p) => (
<option key={p.cca3} value={p.cca3}>{p.name.common}</option>
))}
</select>
{pais && (
<div className="card">
<h3>{pais.name.official}</h3>
<img src={pais.flags.svg} alt={pais.name.common} width="220" />
</div>
)}O que a questão pede
Conceito cobrado
find e renderização condicional por estado.Use cca3 para selecionar e campos name.official e flags.svg para exibir detalhes.
Como pensar
Como validar
const [categorias, setCategorias] = useState([]);
async function carregarCategorias() {
const res = await fetch("https://www.themealdb.com/api/json/v1/1/categories.php");
const data = await res.json();
setCategorias(data.categories || []);
}
<button onClick={carregarCategorias}>Carregar categorias</button>
<div className="grid">
{categorias.map((c) => (
<article key={c.idCategory} className="card">
<h3>{c.strCategory}</h3>
<img src={c.strCategoryThumb} alt={c.strCategory} width="220" />
<p>{c.strCategoryDescription.slice(0, 140)}...</p>
</article>
))}
</div>O que a questão pede
Conceito cobrado
Cada categoria chega em data.categories com idCategory, strCategory, strCategoryThumb e strCategoryDescription.
Como pensar
Como validar
undefined por não usar fallback data.categories || [].const [categoriaSel, setCategoriaSel] = useState("");
const categoria = categorias.find((c) => c.idCategory === categoriaSel);
<select value={categoriaSel} onChange={(e) => setCategoriaSel(e.target.value)}>
<option value="">Selecione</option>
{categorias.map((c) => (
<option key={c.idCategory} value={c.idCategory}>{c.strCategory}</option>
))}
</select>
{categoria ? (
<article className="card">
<h3>{categoria.strCategory}</h3>
<img src={categoria.strCategoryThumb} alt={categoria.strCategory} width="260" />
<p>{categoria.strCategoryDescription}</p>
</article>
) : (
<p>Selecione uma categoria para visualizar os detalhes.</p>
)}O que a questão pede
Conceito cobrado
find + renderização condicional.categoriaSel guarda o identificador; categoria é calculada a partir do array de categorias já carregado.
Como pensar
Como validar
undefined antes da seleção.