Fundamentos de Padrões React
11 exemplos para você começar com Padrões React -- 7 básicos e 4 intermediários.
Busque em todas as páginas da documentação
11 exemplos para você começar com Padrões React -- 7 básicos e 4 intermediários.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Nenhum pacote extra necessário -- cada padrão nesta página é fornecido com o React. Um projeto React padrão (Next.js, Vite ou CRA) com TypeScript é suficiente.
Os padrões abaixo são formas de pensar sobre componentes -- como eles se compõem, como compartilham estado, onde colocar limites de carregamento/erro. Eles não são APIs que você instala; são formas reutilizáveis que você reconhece.
Construa componentes de layout e wrapper aceitando children em vez de herdar ou configurar.
function Card({ children }: { children: React.ReactNode }) {
return <div className="border rounded p-4 shadow">{children}</div>;
}
// Uso
function Page() {
return (
<Card>
<h2>Título</h2>
<p>Qualquer conteúdo que o pai queira passar.</p>
</Card>
);
}children é o que você coloca entre as tags de abertura e fechamento do componente.React.ReactNode aceita strings, números, elementos, fragments ou null -- o tipo "renderizável" mais amplo.<Card header={...} footer={...} />.Relacionado: Composição sobre Herança -- slots, componentes compostos, padrões de layout | Componentes (Fundamentos) -- a prop
childrenem profundidade
Decida quem é o dono do estado do componente: o pai (controlado) ou o próprio componente (não controlado).
import { useState } from "react";
// Controlado: o pai é o dono do valor
function ControlledInput({
value, onChange,
}: {
value: string;
onChange: (v: string) => void;
}) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
// Não controlado: o componente é o dono de seu próprio estado internamente
function UncontrolledInput({ defaultValue = "" }: { defaultValue?: string }) {
const [value, setValue] = useState(defaultValue);
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}value/onChange (controlado) ou defaultValue (não controlado), nunca os misture.Relacionado: Controlado vs Não Controlado -- formato da API, componentes com ambos os modos, casos extremos | Formulários: Controlado vs Não Controlado -- o lado específico de formulários
Compartilhe comportamento passando uma função como filho ou prop, retornando JSX baseado no estado interno.
import { useState } from "react";
function Toggle({
render,
}: {
render: (state: { on: boolean; toggle: () => void }) => React.ReactNode;
}) {
const [on, setOn] = useState(false);
return <>{render({ on, toggle: () => setOn((v) => !v) })}</>;
}
// Uso
function App() {
return (
<Toggle
render={({ on, toggle }) => (
<button onClick={toggle}>{on ? "ON" : "OFF"}</button>
)}
/>
);
}render, children ou algo descritivo (renderItem); seja consistente dentro de uma base de código.Relacionado: Render Props -- quando ainda usá-las, alternativa de hooks | Higher-Order Components -- um padrão de indireção similar
Projete componentes multi-parte que compartilham estado implícito através de context -- o pai coordena, os filhos compõem.
import { createContext, useContext, useState } from "react";
const TabsCtx = createContext<{ active: string; setActive: (v: string) => void; } | null>(null);
function Tabs({ defaultValue, children }: { defaultValue: string; children: React.ReactNode }) {
const [active, setActive] = useState(defaultValue);
return <TabsCtx.Provider value={{ active, setActive }}>{children}</TabsCtx.Provider>;
}
function Tab({ value, children }: { value: string; children: React.ReactNode }) {
const ctx = useContext(TabsCtx)!;
return (
<button onClick={() => ctx.setActive(value)} aria-pressed={ctx.active === value}>
{children}
</button>
);
}
function Panel({ value, children }: { value: string; children: React.ReactNode }) {
const ctx = useContext(TabsCtx)!;
return ctx.active === value ? <div>{children}</div> : null;
}
// Uso
// <Tabs defaultValue="a">
// <Tab value="a">A</Tab><Tab value="b">B</Tab>
// <Panel value="a">Conteúdo A</Panel><Panel value="b">Conteúdo B</Panel>
// </Tabs>Tabs.Tab, Tabs.Panel) para uma API limpa.useContext retornar null, para que o uso indevido falhe ruidosamente.Relacionado: Componentes Compostos -- mergulho profundo, tipagem TypeScript | Padrões de Contexto -- melhores práticas de context
Compartilhe dados através de uma subárvore sem prop drilling; divida contextos por frequência de atualização para evitar renderização excessiva.
import { createContext, useContext, useMemo, useState } from "react";
type Theme = "light" | "dark";
const ThemeCtx = createContext<Theme>("light");
const SetThemeCtx = createContext<(t: Theme) => void>(() => {});
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const set = useMemo(() => setTheme, []);
return (
<ThemeCtx.Provider value={theme}>
<SetThemeCtx.Provider value={set}>{children}</SetThemeCtx.Provider>
</ThemeCtx.Provider>
);
}
function ThemeToggle() {
const theme = useContext(ThemeCtx);
const setTheme = useContext(SetThemeCtx);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme}
</button>
);
}setTheme não re-renderizam quando theme muda.useMemo ou useCallback para que o valor do contexto de escrita permaneça estável referencialmente.Relacionado: Padrões de Contexto -- divisão, otimização, componentes de servidor | useContext -- o hook subjacente | Context vs. Zustand -- quando usar cada um
Capture erros em tempo de renderização em uma subárvore e renderize um fallback em vez de uma tela branca.
"use client";
import { Component, type ReactNode } from "react";
interface State { error: Error | null; }
class ErrorBoundary extends Component<{ children: ReactNode }, State> {
state: State = { error: null };
static getDerivedStateFromError(error: Error): State {
return { error };
}
componentDidCatch(error: Error) {
console.error("Erro na UI:", error);
}
render() {
if (this.state.error) {
return <p role="alert">Algo deu errado: {this.state.error.message}</p>;
}
return this.props.children;
}
}
// Uso
// <ErrorBoundary><BuggyChart /></ErrorBoundary>getDerivedStateFromError ainda não tem um equivalente em hook.react-error-boundary (mais leve, API amigável a hooks) e registre no Sentry/Datadog em componentDidCatch.Relacionado: Error Boundaries -- react-error-boundary,
error.tsxdo Next.js, logging | Suspense -- a contraparte de carregamento
Renderize filhos em um nó DOM fora da árvore do pai -- escape de overflow: hidden e armadilhas de z-index.
"use client";
import { createPortal } from "react-dom";
function Modal({
open, onClose, children,
}: {
open: boolean;
onClose: () => void;
children: React.ReactNode;
}) {
if (!open || typeof document === "undefined") return null;
return createPortal(
<div
onClick={onClose}
style={{
position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)",
display: "grid", placeItems: "center",
}}
>
<div onClick={(e) => e.stopPropagation()} style={{ background: "white", padding: 24 }}>
{children}
</div>
</div>,
document.body,
);
}createPortal(node, container) monta node dentro de container, mas eventos ainda borbulham para o pai React.typeof document === "undefined" para que seja seguro em SSR; caso contrário, document gera um erro no servidor.Relacionado: React Portals -- armadilha de foco, acessibilidade, receitas de z-index | Componente Modal -- padrões de modal de produção
Mostre declarativamente um fallback enquanto um filho assíncrono resolve -- sem passar prop isLoading.
"use client";
import { Suspense, use } from "react";
interface User { id: number; name: string; }
function UserCard({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}
export default function UserPage({
userPromise,
}: {
userPromise: Promise<User>;
}) {
return (
<Suspense fallback={<p>Carregando usuário...</p>}>
<UserCard userPromise={userPromise} />
</Suspense>
);
}<Suspense fallback={...}> captura componentes filhos que "suspendem" (Componentes de Servidor aguardando dados, Componentes de Cliente lendo uma promise com use).<ErrorBoundary> para que promises rejeitadas mostrem uma UI de erro em vez de propagar pela árvore.Relacionado: Suspense Boundaries -- posicionamento, streaming, casos extremos | hook use -- lendo promises e context | Streaming do Next.js -- Suspense em Componentes de Servidor
Envolva um componente para injetar comportamento compartilhado -- autenticação, logging, flags de funcionalidade.
"use client";
import { useEffect } from "react";
function withLogging<P extends object>(
Component: React.ComponentType<P>,
label: string
) {
return function LoggedComponent(props: P) {
useEffect(() => {
console.log(`[${label}] montado`);
return () => console.log(`[${label}] desmontado`);
}, []);
return <Component {...props} />;
};
}
// Uso
function Dashboard({ userId }: { userId: string }) {
return <p>Dashboard para {userId}</p>;
}
const LoggedDashboard = withLogging(Dashboard, "Dashboard");<P extends object>) para que o componente envolvido mantenha seus tipos de prop originais.function LoggedComponent(...)) para que o React DevTools mostre algo útil.Relacionado: Higher-Order Components -- tipagem, armadilhas, quando preferir hooks | Render Props -- o outro padrão de "indireção"
Modele estados complexos de UI como transições explícitas -- sem mais combinações de props "impossíveis".
import { useReducer } from "react";
type Status =
| { kind: "idle" }
| { kind: "loading" }
| { kind: "success"; data: string }
| { kind: "error"; message: string };
type Event =
| { type: "FETCH" }
| { type: "RESOLVE"; data: string }
| { type: "REJECT"; message: string }
| { type: "RESET" };
function reducer(state: Status, event: Event): Status {
switch (state.kind) {
case "idle": return event.type === "FETCH" ? { kind: "loading" } : state;
case "loading": return event.type === "RESOLVE" ? { kind: "success", data: event.data }
: event.type === "REJECT" ? { kind: "error", message: event.message }
: state;
case "success":
case "error": return event.type === "RESET" ? { kind: "idle" } : state;
}
}
export default function DataPanel() {
const [state, dispatch] = useReducer(reducer, { kind: "idle" } as Status);
return (
<div>
{state.kind === "idle" && <button onClick={() => dispatch({ type: "FETCH" })}>Carregar</button>}
{state.kind === "loading" && <p>Carregando...</p>}
{state.kind === "success" && <p>Recebido: {state.data}</p>}
{state.kind === "error" && <p>Erro: {state.message}</p>}
</div>
);
}loading: true, error: "...", data: "x", isLoading: true) impossíveis.useTransition ou useActionState para disparar mudanças de estado a partir de trabalho assíncrono.Relacionado: Máquinas de Estado para Lógica de UI -- exemplos mais aprofundados, integração com XState | useReducer -- o hook por trás disso
Evite que um filho re-renderize quando seu pai re-renderiza, mas suas props não mudaram.
import { memo, useCallback, useState } from "react";
const Row = memo(function Row({
label, onSelect,
}: {
label: string;
onSelect: (label: string) => void;
}) {
return <li onClick={() => onSelect(label)}>{label}</li>;
});
export default function List({ items }: { items: string[] }) {
const [selected, setSelected] = useState<string | null>(null);
// Identidade estável -- useCallback mantém as linhas memoizadas de re-renderizar
const onSelect = useCallback((label: string) => setSelected(label), []);
return (
<>
<p>Selecionado: {selected ?? "nenhum"}</p>
<ul>
{items.map((item) => (
<Row key={item} label={item} onSelect={onSelect} />
))}
</ul>
</>
);
}memo(Component) pula re-renderizações quando as props são referencialmente iguais à renderização anterior.useCallback dá a ela uma identidade estável para que memo possa realmente otimizar.memo.memo/useCallback/useMemo inteiramente -- o compilador cuida disso.Relacionado: Otimização de Performance do React -- profiling, keys, virtualização de listas | Re-renders -- o que dispara renderizações e como reduzi-las | React Compiler -- auto-memoização
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥