Componentes
Crie interfaces de usuário reutilizáveis a partir de componentes de função - o único tipo de componente que você precisa no React moderno.
Busque em todas as páginas da documentação
Crie interfaces de usuário reutilizáveis a partir de componentes de função - o único tipo de componente que você precisa no React moderno.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
// Componente básico com props tipadas
interface GreetingProps {
name: string;
className?: string;
}
function Greeting({ name, className }: GreetingProps) {
return <h1 className={className}>Olá, {name}</h1>;
}
// Componente com children
function Card({ children, title }: { children: React.ReactNode; title: string }) {
return (
<div className="rounded border p-4">
<h2>{title}</h2>
{children}
</div>
);
}
// Props padrão via desestruturação com valores padrão
function Button({ variant = "primary", ...rest }: ButtonProps) {
return <button className={variant} {...rest} />;
}Quando usar isso: Sempre que você tiver uma parte da interface do usuário que aparece mais de uma vez, ou quando uma seção de JSX for complexa o suficiente para merecer seu próprio nome.
"use client";
import { useState } from "react";
// --- Primitivas Reutilizáveis ---
interface BadgeProps {
label: string;
color?: "green" | "yellow" | "red";
}
function Badge({ label, color = "green" }: BadgeProps) {
const colors = {
green: "bg-green-100 text-green-800",
yellow: "bg-yellow-100 text-yellow-800",
red: "bg-red-100 text-red-800",
};
return (
<span className={`inline-block rounded px-2 py-0.5 text-xs font-medium ${colors[color]}`}>
{label}
</span>
);
}
// --- Componente Composto ---
interface Task {
id: number;
title: string;
status: "done" | "in-progress" | "blocked";
}
const statusColor: Record<Task["status"], BadgeProps["color"]> = {
done: "green",
"in-progress": "yellow",
blocked: "red",
};
function TaskItem({ task }: { task: Task }) {
return (
<li className="flex items-center justify-between border-b py-2">
<span>{task.title}</span>
<Badge label={task.status} color={statusColor[task.status]} />
</li>
);
}
// --- Componente Contêiner ---
export function TaskBoard() {
const [tasks] = useState<Task[]>([
{ id: 1, title: "Configurar pipeline de CI", status: "done" },
{ id: 2, title: "Escrever testes de integração", status: "in-progress" },
{ id: 3, title: "Implantar em produção", status: "blocked" },
]);
return (
<section className="max-w-md rounded border p-4">
<h2 className="mb-3 text-lg font-bold">Quadro de Tarefas</h2>
<ul>
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</ul>
</section>
);
}O que isso demonstra:
Badge) compostos em componentes maiores (TaskItem)TaskBoard) possuindo o estado, componentes de apresentação recebendo-o via propsprops e retorna React.ReactNode| Parâmetro | Tipo | Descrição |
|---|---|---|
props | object | Todos os atributos passados para o componente, incluindo children |
props.children | React.ReactNode | JSX aninhado passado entre as tags de abertura e fechamento |
| Retorno | React.ReactNode | JSX, null, string, number, boolean, ou um array desses |
Padrão de slot (children nomeados):
interface LayoutProps {
header: React.ReactNode;
sidebar: React.ReactNode;
children: React.ReactNode;
}
function Layout({ header, sidebar, children }: LayoutProps) {
return (
<div className="grid grid-cols-[250px_1fr] grid-rows-[auto_1fr]">
<header className="col-span-2">{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}Prop de renderização (extraindo lógica):
interface ToggleProps {
children: (isOn: boolean, toggle: () => void) => React.ReactNode;
}
function Toggle({ children }: ToggleProps) {
const [isOn, setIsOn] = useState(false);
return <>{children(isOn, () => setIsOn(prev => !prev))}</>;
}
// Uso
<Toggle>
{(isOn, toggle) => (
<button onClick={toggle}>{isOn ? "LIGADO" : "DESLIGADO"}</button>
)}
</Toggle>Componentes compostos:
function Tabs({ children }: { children: React.ReactNode }) {
const [activeIndex, setActiveIndex] = useState(0);
return <TabsContext.Provider value={{ activeIndex, setActiveIndex }}>{children}</TabsContext.Provider>;
}
Tabs.Panel = function Panel({ index, children }: { index: number; children: React.ReactNode }) {
const { activeIndex } = useContext(TabsContext);
return activeIndex === index ? <div>{children}</div> : null;
};// Tipando um componente que envolve elementos nativos
interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
variant?: "primary" | "secondary";
}
function Button({ variant = "primary", className, ...rest }: ButtonProps) {
return <button className={`btn-${variant} ${className ?? ""}`} {...rest} />;
}
// Atalho React.PropsWithChildren
type CardProps = React.PropsWithChildren<{ title: string }>;Definir componentes dentro de componentes - Declarar function Inner() {} dentro de function Outer() {} cria uma nova identidade de componente a cada renderização, destruindo o estado de Inner a cada vez. Correção: Mova Inner para fora de Outer, ou use useMemo se você realmente precisar de um closure sobre o escopo pai.
Mutar props - props.items.push(newItem) muta os dados do pai, levando a bugs sutis. Correção: Trate props como imutáveis. Crie novos arrays/objetos e eleve as mudanças de estado para o proprietário.
Falta de key ao mapear - O React avisa e pode perder o estado quando os itens não têm chaves. Correção: Forneça uma key estável e única de seus dados (não o índice do array, a menos que a lista seja estática).
Abstração excessiva prematura - Criar <FlexRow>, <Spacer> e <Text> para cada elemento adiciona indireção sem o benefício da reutilização. Correção: Espere até usar um padrão três ou mais vezes antes de extrair um componente.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Componentes de Servidor (RSC) | O componente apenas lê dados e renderiza HTML estático - sem hooks, sem manipuladores de eventos | Você precisa de useState, useEffect ou APIs do navegador |
| Web Components | Você precisa de elementos personalizados agnósticos de framework compartilhados entre React, Vue, etc. | Você está totalmente em uma base de código React |
| Partials de Template (Handlebars) | Páginas renderizadas no servidor sem um framework JS | Você precisa de interatividade no lado do cliente |
De uma aplicação SaaS de produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: BackButton com props tipadas e padrões
// Arquivo: src/components/services/back-button.tsx
'use client';
import { useRouter } from 'next/navigation';
import { ArrowLeft } from 'lucide-react';
import { useLoadingStore } from '@/stores/loading-store';
interface BackButtonProps {
href?: string;
label?: string;
}
export default function BackButton({
href,
label = 'Voltar às seções',
}: BackButtonProps) {
const router = useRouter();
const startLoading = useLoadingStore((state) => state.startLoading);
const handleClick = () => {
startLoading();
if (href) {
router.push(href);
} else {
router.back();
}
};
return (
<button onClick={handleClick} className="flex items-center gap-2 text-sm font-medium cursor-pointer transition-colors">
<ArrowLeft className="h-4 w-4" />
{label}
</button>
);
}O que isso demonstra em produção:
? permitem uso flexível. A prop href é opcional para que o botão possa navegar para uma rota específica ou voltar na história.label = 'Voltar às seções') fornecem fallbacks sensatos sem a necessidade de uma declaração defaultProps separada.(state) => state.startLoading se inscreve apenas na ação startLoading, evitando re-renderizações quando outros estados da loja de carregamento mudam.interface é preferível a type para props porque interfaces produzem melhores mensagens de erro e suportam a fusão de declarações, se necessário.Um componente é uma função que retorna React.ReactNode (JSX, null, string, etc.) e é chamada pelo React durante a renderização. Funções regulares são chamadas pelo seu código. Componentes devem começar com uma letra maiúscula para que o JSX os trate como componentes, não como elementos HTML.
Prefira interface para props de componentes. Interfaces produzem melhores mensagens de erro do TypeScript e suportam a fusão de declarações. Use type apenas quando precisar de tipos de união ou tipos mapeados.
Use padrões de desestruturação na assinatura da função:
function Button({ variant = "primary" }: ButtonProps) {
return <button className={variant}>Clique</button>;
}children é uma prop especial que contém qualquer JSX aninhado entre as tags de abertura e fechamento de um componente. Digite-a como React.ReactNode. Use-a para componentes wrapper/layout que não conhecem seu conteúdo com antecedência.
Tecnicamente sim, mas não faça isso. O React cria uma nova identidade de componente a cada renderização, destruindo todo o estado no componente interno. Sempre defina componentes no nível do módulo.
Em vez de uma única prop children, aceite múltiplas props nomeadas tipadas como React.ReactNode:
function Layout({ header, sidebar, children }: LayoutProps) {
return (
<div>
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}Use React.ComponentPropsWithoutRef<"element">:
interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
variant?: "primary" | "secondary";
}useState ou useReducer)Componentes de Servidor são executados apenas no servidor, podem usar await para dados diretamente, têm zero JavaScript no lado do cliente e não podem usar hooks ou manipuladores de eventos. Componentes regulares (cliente) são executados no navegador e suportam interatividade.
Um tipo utilitário que adiciona children?: React.ReactNode ao seu tipo de props:
type CardProps = React.PropsWithChildren<{ title: string }>;Use-o quando quiser um atalho em vez de digitar children manualmente.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥