Renderização Condicional
Mostre, oculte ou troque a interface do usuário com base no estado, props ou valores computados.
Busque em todas as páginas da documentação
Mostre, oculte ou troque a interface do usuário com base no estado, props ou valores computados.
🤖 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.
// Ternário - escolha entre dois elementos
{isLoggedIn ? <Dashboard /> : <LoginForm />}
// E lógico - mostrar ou nada
{hasNotifications && <Badge count={notifications.length} />}
// Retorno antecipado - saia de todo o componente
if (!user) return <Skeleton />;
return <Profile user={user} />;
// Variável extraída - legível quando a lógica é complexa
const content = status === "loading"
? <Spinner />
: status === "error"
? <ErrorMessage />
: <DataTable rows={data} />;
return <section>{content}</section>;Quando usar isso: Sempre que a interface do usuário muda com base em uma condição - estados de carregamento, portões de autenticação, flags de recursos, estados vazios.
"use client";
import { useState } from "react";
type Status = "idle" | "loading" | "success" | "error";
interface FetchResult {
status: Status;
data?: string[];
error?: string;
}
export function FetchDemo() {
const [result, setResult] = useState<FetchResult>({ status: "idle" });
async function handleFetch() {
setResult({ status: "loading" });
try {
// Simula requisição de rede
await new Promise(resolve => setTimeout(resolve, 1000));
const success = Math.random() > 0.3;
if (!success) throw new Error("Network timeout");
setResult({
status: "success",
data: ["React", "Next.js", "TypeScript"],
});
} catch (err) {
setResult({
status: "error",
error: err instanceof Error ? err.message : "Unknown error",
});
}
}
return (
<div className="max-w-sm space-y-4 rounded border p-4">
<button
onClick={handleFetch}
disabled={result.status === "loading"}
className="rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
>
{result.status === "loading" ? "Fetching..." : "Fetch Data"}
</button>
{result.status === "idle" && (
<p className="text-gray-500">Pressione o botão para carregar os dados.</p>
)}
{result.status === "loading" && (
<div className="animate-pulse rounded bg-gray-200 p-4">Loading...</div>
)}
{result.status === "error" && (
<div className="rounded bg-red-50 p-3 text-red-700">
<strong>Erro:</strong> {result.error}
</div>
)}
{result.status === "success" && result.data && (
<ul className="list-inside list-disc">
{result.data.map(item => (
<li key={item}>{item}</li>
))}
</ul>
)}
</div>
);
}O que isso demonstra:
Status) controlando quatro estados de UI distintos&& para renderizar cada branch de estado independentementeresult.data é acessado com segurança apenas no branch "success"false, null, undefined e true são filhos JSX válidos que renderizam nada, é por isso que && e ternários funcionam<Spinner /> para <DataTable />), o React desmonta a árvore antiga e monta uma nova| Padrão | Melhor Para | Exemplo |
|---|---|---|
Ternário ? : | Escolher entre dois elementos | {ok ? <A /> : <B />} |
&& | Mostrar ou ocultar um elemento | {show && <Modal />} |
| Retorno antecipado | Proteger todo o componente | if (!data) return null; |
| Extração de variável | Lógica complexa de múltiplos branches | const ui = ...; |
| Objeto de consulta/mapa | Muitos estados discretos | statusMap[status] |
const statusUI: Record<Status, React.ReactNode> = {
idle: <p>Esperando...</p>,
loading: <Spinner />,
success: <DataTable />,
error: <ErrorBanner />,
};
return <div>{statusUI[status]}</div>;// Unions discriminadas tornam a renderização condicional type-safe
type Result =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; error: string };
function Display({ result }: { result: Result }) {
switch (result.status) {
case "idle":
return <p>Pronto</p>;
case "loading":
return <Spinner />;
case "success":
// result.data é estreitado para string[] aqui
return <List items={result.data} />;
case "error":
// result.error é estreitado para string aqui
return <Alert message={result.error} />;
}
}0 && renderiza um 0 literal - {count && <Badge />} exibe 0 na tela quando a contagem é zero porque 0 é um valor falsy, mas renderizável. Correção: Use {count > 0 && <Badge />} ou um ternário.
Retorno null ausente - Um componente que retorna JSX condicionalmente, mas não tem um retorno de fallback, implicitamente retorna undefined, o que funciona, mas aciona avisos de linting. Correção: return null explicitamente para o caso vazio.
Redefinição de estado na mudança de tipo - Alternar entre <input type="text" /> e <textarea /> na mesma posição da árvore destrói o estado porque são tipos de elemento diferentes. Correção: Se precisar preservar o estado, renderize ambos e alterne a visibilidade com CSS, ou eleve o estado.
Ternários aninhados - {a ? b ? <X /> : <Y /> : <Z />} é difícil de ler e fácil de interpretar mal. Correção: Extraia para uma variável ou use um padrão de switch/mapa.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
CSS display: none | Você quer manter o componente montado e preservar seu estado (por exemplo, painéis de abas) | O conteúdo oculto é caro para renderizar ou envolve requisições de rede |
<Suspense> + lazy | Carregamento condicional de um chunk inteiro dividido em código | Simples mostrar/ocultar dentro de um componente já carregado |
| Renderização baseada em rota | Páginas diferentes que cada uma tem sua própria URL | Alternar dentro de uma única visualização |
De uma aplicação SaaS de produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: exibição de banner com múltiplas cláusulas de guarda
// Arquivo: src/components/banner/banner-display.tsx
export default function BannerDisplay({ location, className }: BannerDisplayProps) {
const { banners, loading, error, fetchBanners } = useBannerStore();
const locationBanners = banners[location] || [];
useEffect(() => {
fetchBanners(location);
}, [location]);
// Cláusula de guarda 1: carregando sem dados em cache
if (loading && locationBanners.length === 0) {
return null;
}
// Cláusula de guarda 2: estado de erro
if (error) {
return null;
}
// Cláusula de guarda 3: sem banners para esta localização
if (locationBanners.length === 0) {
return null;
}
// Caminho feliz: renderiza banners
return (
<div className={cn('space-y-4', className)}>
{locationBanners.map((banner) => (
<div
key={banner.id}
className={cn(
'rounded-md py-2 px-4 shadow-sm',
getBorderClass(banner.borderStyle),
'bg-zinc-50 dark:bg-zinc-900',
banner.customStyles
)}
>
{banner.content}
</div>
))}
</div>
);
}O que isso demonstra em produção:
return null antecipados agem como cláusulas de guarda, impedindo a renderização quando nada útil pode ser mostradoloading && locationBanners.length === 0 significa não mostrar nada ao carregar pela primeira vez, mas continuar mostrando banners antigos durante uma atualizaçãobanners[location] || [] fornece um fallback seguro para que .length nunca lance um erro em undefinedcn() (clsx + tailwind-merge) une condicionalmente nomes de classeUse um ternário {condition ? <A /> : <B />} para escolher entre dois elementos, ou {condition && <A />} para mostrar ou ocultar um elemento. Para lógica complexa de múltiplos branches, use uma variável ou objeto de consulta antes do retorno.
O operador && do JavaScript retorna o primeiro valor falsy. 0 é falsy, mas é um filho React válido que renderiza como texto. Use {count > 0 && <Component />} ou um ternário em vez disso.
Mapeie valores de estado para elementos JSX usando um Record:
const statusUI: Record<Status, React.ReactNode> = {
idle: <p>Esperando...</p>,
loading: <Spinner />,
success: <DataTable />,
error: <ErrorBanner />,
};
return <div>{statusUI[status]}</div>;Sim, se o tipo do componente em uma posição da árvore mudar (por exemplo, <Spinner /> para <DataTable />), o React desmonta o componente antigo e monta um novo, destruindo todo o estado. Se apenas as props mudarem no mesmo tipo de componente, o React atualiza no local.
display: noneUma union TypeScript onde cada membro tem um campo status literal. O switch ou if em status estreita o tipo, então o TypeScript sabe quais campos estão disponíveis em cada branch - eliminando verificações de undefined.
Sim, eles são difíceis de ler. Para mais de dois branches, extraia a lógica para uma variável, use uma instrução switch ou use o padrão de objeto de consulta.
display: none quando quiser manter o componente montado e preservar seu estado (por exemplo, painéis de abas)Retorne null. Isso é mais limpo do que retornar um Fragment vazio <></> e é o padrão para componentes que condicionalmente não têm saída.
Não diretamente dentro do JSX. Extraia para uma variável ou uma função auxiliar:
function getStatusIcon(status: Status) {
switch (status) {
case "success": return <CheckIcon />;
case "error": return <XIcon />;
default: return null;
}
}
return <div>{getStatusIcon(status)}</div>;Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥