React
Renderizado condicional
Explica cómo mostrar, ocultar y alternar ramas de interfaz con if, operadores ternarios, &&, retornos tempranos y componentes especializados.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
React
Explica cómo mostrar, ocultar y alternar ramas de interfaz con if, operadores ternarios, &&, retornos tempranos y componentes especializados.
El renderizado condicional consiste en producir árboles de interfaz diferentes según los datos actuales. React no añade una sintaxis especial: utiliza decisiones normales de JavaScript y después reconcilia el resultado con el render anterior.
Un componente puede representar varios estados posibles. Su responsabilidad es devolver el árbol correspondiente al estado actual.
type ProfileProps = {
user: User | null;
};
function Profile({ user }: ProfileProps) {
if (!user) {
return <LoginMessage />;
}
return <UserProfile user={user} />;
}El componente no oculta y muestra manualmente nodos existentes. En cada render calcula una de dos descripciones:
user === null
↓
<LoginMessage />
user disponible
↓
<UserProfile user={user} />React compara la rama nueva con la anterior y decide qué preservar, actualizar, montar o desmontar.
Las interfaces reales no tienen un único aspecto. Una misma región puede encontrarse:
Sin un modelo claro, estas condiciones terminan dispersas en clases CSS, mutaciones del DOM y combinaciones imposibles. El enfoque declarativo concentra la decisión en el render: para cada estado válido debe existir una salida coherente.
No pienses solamente en “mostrar este botón”. Piensa en qué estados puede adoptar la región completa.
idle
↓
formulario editable
submitting
↓
formulario bloqueado + progreso
success
↓
confirmación
error
↓
formulario + mensaje recuperableCada estado debe tener datos suficientes para producir una interfaz consistente. Cuando varios booleanos pueden contradecirse, suele ser mejor modelar un estado explícito:
type SubmissionState =
| { status: "idle" }
| { status: "submitting" }
| { status: "success"; orderId: string }
| { status: "error"; message: string };Esto impide representar combinaciones como isLoading === true y isSuccess === true al mismo tiempo sin una intención definida.
Los retornos tempranos son útiles cuando una condición reemplaza toda la salida principal.
function OrdersPage({ result }: { result: OrdersResult }) {
if (result.status === "loading") {
return <OrdersSkeleton />;
}
if (result.status === "error") {
return <ErrorState message={result.message} />;
}
if (result.orders.length === 0) {
return <EmptyOrders />;
}
return <OrderList orders={result.orders} />;
}El flujo es fácil de leer porque cada caso completo termina antes de llegar al siguiente.
Si todas las ramas deben compartir layout, navegación o contexto, devolver demasiado pronto puede duplicar estructura:
function Page({ state }: Props) {
return (
<PageLayout>
<PageHeader />
<main>{renderContent(state)}</main>
</PageLayout>
);
}En ese caso conviene mantener el marco común y condicionar únicamente la región variable.
El ternario expresa una decisión entre dos valores:
<p>{isActive ? "Activo" : "Inactivo"}</p>También puede seleccionar componentes:
{isEditing ? (
<ProductForm product={product} />
) : (
<ProductDetails product={product} />
)}Es apropiado cuando ambas ramas son cortas y forman una única decisión. Los ternarios anidados suelen ser difíciles de seguir:
{isLoading ? <Spinner /> : error ? <Error /> : data ? <Result /> : null}No es técnicamente incorrecto, pero oculta el modelo de estados. Un retorno temprano, una variable descriptiva o una unión discriminada suele comunicar mejor la intención.
El operador && sirve cuando solo quieres renderizar algo si la condición es verdadera:
{errorMessage && <ErrorMessage message={errorMessage} />}JavaScript devuelve el primer operando falsy o el segundo operando. React no convierte automáticamente el resultado a booleano.
{items.length && <ProductList items={items} />}Cuando items.length vale 0, el resultado de la expresión es el número 0, y React sí renderiza números. La versión segura expresa una condición booleana:
{items.length > 0 && <ProductList items={items} />}Este detalle también importa con valores como NaN o strings vacíos. Antes de usar &&, pregúntate cuál es exactamente el valor que puede devolver el lado izquierdo.
Un componente puede devolver null para no producir contenido visible:
function ValidationHint({ message }: { message?: string }) {
if (!message) return null;
return <p role="alert">{message}</p>;
}null no significa que el componente deje de ejecutarse. El padre puede seguir renderizándolo y React seguirá evaluando su función. Solo significa que ese render no produce nodos visibles.
Si el componente ya estaba montado y luego devuelve null, sus descendientes visibles se desmontan. El estado propio del componente puede preservarse mientras su identidad siga existiendo en la misma posición, pero los componentes que ya no devuelve dejan de formar parte del árbol.
Estas dos estrategias tienen efectos distintos.
{isOpen && <FiltersPanel />}Cuando isOpen pasa a false, FiltersPanel sale del árbol. Su estado local y sus effects se limpian.
<div hidden={!isOpen}>
<FiltersPanel />
</div>El componente sigue montado. Conserva estado y suscripciones, aunque no sea visible.
La decisión depende del producto:
Para interfaces avanzadas, APIs como Activity pueden preservar UI oculta con un comportamiento específico, pero deben estudiarse por separado y según la versión de React.
React preserva estado según el tipo, la posición y la key del elemento.
function AccountPanel({ isCompany }: { isCompany: boolean }) {
return isCompany ? <TaxForm /> : <TaxForm />;
}Aunque el JSX aparezca en ramas distintas, React puede ver el mismo tipo en la misma posición y preservar su estado.
Para reiniciarlo deliberadamente, cambia la key:
<TaxForm key={isCompany ? "company" : "person"} />Si las ramas producen tipos diferentes, React desmonta uno y monta el otro:
{isEditing ? <EditForm /> : <ReadOnlyView />}Este comportamiento conecta el render condicional con reconciliación y preservación del estado.
Una consulta de datos no debe reducirse a loading o data. Considera al menos:
type ProductsState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; products: Product[] }
| { status: "error"; message: string };function ProductsContent({ state }: { state: ProductsState }) {
switch (state.status) {
case "idle":
return <p>Inicia una búsqueda.</p>;
case "loading":
return <ProductsSkeleton />;
case "error":
return <ErrorState message={state.message} />;
case "success":
return state.products.length === 0
? <EmptyProducts />
: <ProductList products={state.products} />;
}
}Aquí el estado vacío pertenece al éxito: la operación funcionó, pero no produjo resultados. Confundirlo con error genera mensajes y acciones incorrectas.
type OrderActionsProps = {
order: Order;
currentUser: User;
};
function OrderActions({ order, currentUser }: OrderActionsProps) {
const canCancel =
currentUser.permissions.includes("cancel:order") &&
order.status === "pending";
if (order.status === "cancelled") {
return <p>Este pedido ya fue cancelado.</p>;
}
if (!canCancel) {
return null;
}
return (
<button type="button" onClick={() => cancelOrder(order.id)}>
Cancelar pedido
</button>
);
}Este último punto es importante: ocultar una acción mejora la experiencia, pero no proporciona seguridad.
Diferencia entre “todavía no cargados”, “carga exitosa sin resultados” y “dato requerido ausente”. Cada caso necesita una interfaz distinta.
Un componente puede mostrar un estado de error local, pero no debería intentar adivinar silenciosamente datos imposibles. Los tipos y la validación de runtime deben impedir estados inválidos antes del render cuando sea posible.
Cuando varios if independientes pueden renderizar mensajes contradictorios, revisa el modelo. Una máquina de estados o una unión discriminada puede representar mejor la exclusividad.
La interfaz puede mostrar temporalmente un resultado optimista. Si el servidor rechaza la acción, el estado debe volver atrás o mostrar una recuperación clara. El render condicional debe representar también el estado de reversión.
Montar y desmontar componentes costosos repetidamente puede tener coste. Mide antes de optimizar y decide si preservar, ocultar o virtualizar aporta valor real.
{isLoading && <Spinner />}
{data && <Results data={data} />}
{error && <ErrorMessage />}Si los tres valores pueden coexistir, la pantalla puede mostrar carga, datos y error al mismo tiempo. Modela estados mutuamente excluyentes.
Dificulta comprobar qué ramas existen y cuáles faltan. Extrae decisiones o usa switch cuando el dominio lo requiera.
Puede reiniciar formularios, cerrar conexiones o perder estado local inesperadamente.
Convierte la condición a booleano explícito.
Una lista vacía no debería dejar una pantalla en blanco sin explicación o siguiente acción.
Una condición de permisos solo controla la interfaz. El servidor debe validar toda operación sensible.
Valores como hasProducts = products.length > 0 pueden derivarse durante render. Duplicarlos crea fuentes de verdad innecesarias.
&&****: una pieza opcional depende de una condición booleana real.switch****: el componente representa varios estados explícitos y mutuamente excluyentes.null****: la ausencia de representación es una salida válida y deliberada.&& devuelve valores, no necesariamente booleanos; 0 se renderiza.{items.length && <List />} puede mostrar un cero?hidden y retirar un componente del árbol?&& devuelve 0 cuando la longitud es cero, y React renderiza números.hidden conserva el componente montado; retirarlo desmonta sus descendientes y limpia sus effects.key del elemento dentro del árbol.Props explica cómo los componentes reciben la información que determina muchas de estas ramas y cómo comunican acciones hacia sus padres.