Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Componente composto: Select com estado compartilhado
const SelectContext = createContext<{
value: string;
onChange: (value: string) => void;
} | null>(null);
function Select({ value, onChange, children }: {
value: string;
onChange: (value: string) => void;
children: React.ReactNode;
}) {
return (
<SelectContext value={{ value, onChange }}>
<div role="listbox">{children}</div>
</SelectContext>
);
}
function Option({ value, children }: { value: string; children: React.ReactNode }) {
const ctx = use(SelectContext);
if (!ctx) throw new Error("Option must be used within Select");
const isSelected = ctx.value === value;
return (
<div
role="option"
aria-selected={isSelected}
onClick={() => ctx.onChange(value)}
className={isSelected ? "bg-blue-100 font-semibold" : ""}
>
{children}
</div>
);
}
Select.Option = Option;
// Uso - lê como uma API declarativa
<Select value={selected} onChange={setSelected}>
<Select.Option value="react">React</Select.Option>
<Select.Option value="vue">Vue</Select.Option>
<Select.Option value="svelte">Svelte</Select.Option>
</Select>Quando usar isso: Ao construir um componente de múltiplas partes onde subcomponentes precisam de estado compartilhado, mas o consumidor deve controlar a estrutura e a ordem. Pense em <Tabs>/<Tab>, <Accordion>/<AccordionItem>, <Menu>/<MenuItem>.
import { createContext, use, useState, useId, type ReactNode } from "react";
// --- Componente composto Accordion ---
interface AccordionContextValue {
openItems: Set<string>;
toggle: (id: string) => void;
multiple: boolean;
}
const AccordionContext = createContext<AccordionContextValue | null>(null);
function useAccordion() {
const ctx = use(AccordionContext);
if (!ctx) throw new Error("Subcomponentes do Accordion devem ser usados dentro de <Accordion>");
return ctx;
}
// Raiz
function Accordion({
children,
multiple = false,
defaultOpen = [],
}: {
children: ReactNode;
multiple?: boolean;
defaultOpen?: string[];
}) {
const [openItems, setOpenItems] = useState<Set<string>>(
() => new Set(defaultOpen)
);
const toggle = (id: string) => {
setOpenItems((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
if (!multiple) next.clear();
next.add(id);
}
return next;
});
};
return (
<AccordionContext value={{ openItems, toggle, multiple }}>
<div className="divide-y border rounded-lg">{children}</div>
</AccordionContext>
);
}
// Item
interface ItemContextValue {
itemId: string;
isOpen: boolean;
}
const ItemContext = createContext<ItemContextValue | null>(null);
function Item({ id, children }: { id: string; children: ReactNode }) {
const { openItems } = useAccordion();
const isOpen = openItems.has(id);
return (
<ItemContext value={{ itemId: id, isOpen }}>
<div>{children}</div>
</ItemContext>
);
}
function useItem() {
const ctx = use(ItemContext);
if (!ctx) throw new Error("Deve ser usado dentro de <Accordion.Item>");
return ctx;
}
// Trigger
function Trigger({ children }: { children: ReactNode }) {
const { toggle } = useAccordion();
const { itemId, isOpen } = useItem();
const contentId = `accordion-content-${itemId}`;
return (
<button
className="w-full text-left p-4 flex justify-between items-center"
onClick={() => toggle(itemId)}
aria-expanded={isOpen}
aria-controls={contentId}
>
{children}
<span className={`transition-transform ${isOpen ? "rotate-180" : ""}`}>
▼
</span>
</button>
);
}
// Content
function Content({ children }: { children: ReactNode }) {
const { itemId, isOpen } = useItem();
const contentId = `accordion-content-${itemId}`;
if (!isOpen) return null;
return (
<div id={contentId} role="region" className="p-4 pt-0 text-gray-600">
{children}
</div>
);
}
// Anexa subcomponentes
Accordion.Item = Item;
Accordion.Trigger = Trigger;
Accordion.Content = Content;
// --- Uso ---
function FAQPage() {
return (
<Accordion defaultOpen={["general"]}>
<Accordion.Item id="general">
<Accordion.Trigger>O que é React?</Accordion.Trigger>
<Accordion.Content>
React é uma biblioteca JavaScript para construir interfaces de usuário.
</Accordion.Content>
</Accordion.Item>
<Accordion.Item id="hooks">
<Accordion.Trigger>O que são hooks?</Accordion.Trigger>
<Accordion.Content>
Hooks permitem que você use estado e outros recursos do React em componentes de função.
</Accordion.Content>
</Accordion.Item>
</Accordion>
);
}O que isso demonstra:
AccordionContext para estado compartilhado, ItemContext para estado por itemuse() (React 19) ou useContext().Select.Option).| Papel do Componente | Responsabilidades |
|---|---|
Raiz (por exemplo, Accordion) | Possui estado compartilhado, fornece contexto, renderiza o contêiner externo |
Wrapper do item (por exemplo, Accordion.Item) | Escopa o contexto por item, mapeia a identidade do item |
Gatilho (por exemplo, Accordion.Trigger) | Lida com a interação do usuário, conecta-se ao estado compartilhado |
Conteúdo (por exemplo, Accordion.Content) | Renderiza condicionalmente com base no estado compartilhado |
Validação flexível de filhos - aceita subcomponentes em qualquer lugar da árvore, não apenas como filhos diretos:
// Componentes compostos baseados em contexto funcionam em qualquer profundidade
<Accordion>
<div className="custom-wrapper">
{/* Funciona porque o Item lê o contexto, não os filhos diretos */}
<Accordion.Item id="nested">
<Accordion.Trigger>Ainda funciona</Accordion.Trigger>
<Accordion.Content>O contexto flui através de qualquer profundidade</Accordion.Content>
</Accordion.Item>
</div>
</Accordion>Componente composto controlado - permite que o pai controle o estado aberto:
function Accordion({
openItems,
onToggle,
children,
}: {
openItems: Set<string>;
onToggle: (id: string) => void;
children: ReactNode;
}) {
return (
<AccordionContext value={{ openItems, toggle: onToggle, multiple: true }}>
<div>{children}</div>
</AccordionContext>
);
}null e verifique null no hook do consumidor.Subcomponente usado fora do pai - O contexto será null, causando bugs silenciosos ou falhas. Correção: Lance um erro descritivo no hook personalizado quando o contexto for null.
Contexto obsoleto com filhos memoizados - Se um filho for envolvido em React.memo, ele pode não ser re-renderizado quando o contexto mudar. Correção: Certifique-se de que os filhos memoizados ainda consumam o contexto diretamente, não através de props.
Divisão excessiva de contexto - Criar muitas camadas de contexto adiciona complexidade. Correção: Comece com um único contexto; divida apenas quando a análise de desempenho revelar re-renderizações desnecessárias.
Incompatibilidade com componentes de servidor - Componentes compostos que usam contexto exigem "use client". Correção: Marque o arquivo do componente composto com "use client" e aceite filhos de Componentes de Servidor via ReactNode.
| Abordagem | Compensação |
|---|---|
| Componentes compostos | API bonita, flexível; requer configuração de contexto |
| Objeto de configuração | <Tabs items={[...]}/> - mais simples, mas controle de layout menos flexível |
| Render props | Fluxo de dados explícito; mais boilerplate no local de chamada |
| Hooks headless | Nenhuma opinião sobre JSX; o consumidor constrói tudo do zero |
| Composição baseada em slots | Mais simples; as partes subcomponentes não podem se comunicar sem prop drilling |
<Tabs>/<Tab>, <Accordion>/<AccordionItem> e <Select>/<Option>.use() (React 19) ou useContext().<Select.Option> é lido como uma relação declarativa.// Nível 1: AccordionContext - estado compartilhado (openItems, toggle)
// Nível 2: ItemContext - estado por item (itemId, isOpen)
<AccordionContext value={{ openItems, toggle, multiple }}>
<ItemContext value={{ itemId: id, isOpen }}>
{children}
</ItemContext>
</AccordionContext>AccordionContext fornece estado compartilhado para todos os itens.ItemContext escopa a identidade por item para que Trigger e Content saibam a qual item pertencem.null, causando bugs silenciosos ou falhas.null.if (!ctx) throw new Error("Option must be used within Select");createContext, use() ou useState."use client" e aceite filhos de Componentes de Servidor via ReactNode.interface AccordionContextValue {
openItems: Set<string>;
toggle: (id: string) => void;
multiple: boolean;
}
const AccordionContext = createContext<AccordionContextValue | null>(null);null e use null como padrão.null no hook personalizado e lance se estiver faltando.Accordion.Item = Item pode causar erros de tipo porque os componentes de função não têm um tipo de propriedade estática por padrão.<div> personalizados ainda consumirá o contexto do pai.React.Children para inspecionar filhos diretos.function Accordion({
openItems,
onToggle,
children,
}: {
openItems: Set<string>;
onToggle: (id: string) => void;
children: ReactNode;
}) {
return (
<AccordionContext value={{ openItems, toggle: onToggle, multiple: true }}>
<div>{children}</div>
</AccordionContext>
);
}<Tabs items={[...]} />) quando o consumidor não precisar de controle sobre layout ou estrutura.useMemo para manter sua referência estável.Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥