Inicio / Artículos / Manejo de estados de interfaz de usuario en el mundo real con renderizado condicional en React

Manejo de estados de interfaz de usuario en el mundo real con renderizado condicional en React

Aprenda cómo crear interfaces de usuario para autenticación, roles, permisos, carga, errores y estado vacío en React utilizando patrones prácticos de renderizado condicional.

2186 palabras

En el capítulo anterior, abordaste los conceptos básicos del renderizado condicional: cómo utilizar condiciones simples para indicarle a React si debe mostrar un componente, otro componente o nada en absoluto.

No obstante, las aplicaciones reales rara vez son tan sencillas:

isLoggedIn ? <Dashboard /> : <Login />

Imagina un panel de control real. Un usuario puede encontrarse en cualquiera de estos estados:

  • Desconectado
  • En proceso de conexión
  • Conectado
  • Administrador
  • Usuario regular
  • Faltante un permiso necesario
  • Esperando la llegada de datos
  • Manejo de un error en la API
  • Visualizando un conjunto de resultados vacío

Cada uno de estos escenarios requiere una interfaz de usuario distinta. Aquí es precisamente donde el renderizado condicional deja de ser un truco sencillo y se convierte en una herramienta real para estructurar tu aplicación.

Echemos un vistazo a cómo se aplica esto en código React de nivel profesional.

1. Renderizado basado en autenticación

Un caso de uso clásico es la autenticación. Imagine una aplicación con dos pantallas posibles:

Not Logged In
      ↓
Login Page

Logged In
      ↓
Dashboard

React puede elegir entre ellas sin mucho esfuerzo:

function App() {
  const isLoggedIn = true;
return (
    <>
      {isLoggedIn ? <Dashboard /> : <Login />}
    </>
  );
}

No obstante, las aplicaciones reales suelen necesitar un tercer estado: loading, ya que la aplicación puede tardar un momento en confirmar si el token del usuario es válido. El flujo entonces sería este:

Checking Authentication
        ↓
      Loading
        ↓
  Authenticated?
   ↙          ↘
 YES          NO
 ↓             ↓
Dashboard     Login

En código, eso podría traducirse a:

function App() {
  const isLoading = false;
  const isLoggedIn = true;
if (isLoading) {
    return <LoadingSpinner />;
  }
  return isLoggedIn
    ? <Dashboard />
    : <Login />;
}

Verá este patrón exacto repetido en innumerables aplicaciones React en producción.

2. Renderizado basado en roles

La autenticación le indica quién está conectado. La autorización le indica qué puede hacer esa persona.

Tomemos como ejemplo una herramienta de gestión de empleados. Un administrador podría ver:

View Employees
Add Employee
Edit Employee
Delete Employee

Mientras que un usuario estándar solo ve:

View Employees

Puede mostrar acciones de forma condicional según el rol del usuario:

function EmployeeCard({ userRole }) {
  return (
    <div>
      <h2>Employee Details</h2>
      <button>View</button>
      {userRole === "admin" && (
        <>
          <button>Edit</button>
          <button>Delete</button>
        </>
      )}
    </div>
  );
}

Con esta configuración, los controles exclusivos para administradores solo aparecen para ellos.

Nota importante de seguridad

El renderizado condicional determina qué se muestra en la interfaz de usuario, pero ocultar un elemento no sustituye a una verdadera seguridad. Por ejemplo:

{isAdmin && <DeleteButton />}

Esto impide que un usuario regular vea el botón, pero el servidor aún debe confirmar de forma independiente que la solicitud proviene realmente de alguien autorizado para eliminar datos. Piense en esto de esta manera:

Frontend
↓
Controls what users SEE

Backend
↓
Controls what users CAN DO

Nunca considere el renderizado condicional del lado del cliente como su mecanismo de autorización.

3. Interfaz de usuario basada en permisos

Las aplicaciones más grandes suelen necesitar un nivel de detalle mayor que los roles simples como:

Admin
User

En su lugar, puede definir permisos específicos como:

CAN_VIEW_USERS
CAN_EDIT_USERS
CAN_DELETE_USERS
CAN_EXPORT_REPORT

Los componentes pueden entonces renderizar cada acción de forma individual según los permisos disponibles:

function UserActions({ permissions }) {
  return (
    <>
      {permissions.includes("CAN_EDIT_USERS") && (
        <button>Edit</button>
      )}
      {permissions.includes("CAN_DELETE_USERS") && (
        <button>Delete</button>
      )}
    </>
  );
}

Este enfoque permite un control mucho más preciso sobre lo que puede hacer cada usuario.

4. Estados de carga

Supongamos que un panel de control envía una llamada a API que tarda dos segundos en responder. ¿Qué debería aparecer en la pantalla durante ese tiempo? Ciertamente no una página en blanco; lo que se desea es un indicador de carga:

if (loading) {
  return <p>Loading products...</p>;
}

El flujo general es el siguiente:

API Request
    ↓
Loading = true
    ↓
Show Loader
    ↓
API Response
    ↓
Loading = false
    ↓
Show Content

Los indicadores de carga hacen que una aplicación parezca receptiva incluso cuando la red es lenta.

5. Cargadores esqueléticos

En lugar de un mensaje de texto simple como:

Loading...

muchas interfaces modernas muestran un marcador de posición con la forma del contenido que está a punto de cargarse: un cargador esquelético. Por ejemplo:

┌──────────────────────┐
│ █████████████        │
│ ███████              │
│ █████████████████    │
└──────────────────────┘

Una vez que los datos reales regresan, reemplazan al marcador de posición:

┌──────────────────────┐
│ MacBook Air          │
│ ₹99,999              │
│ ⭐⭐⭐⭐⭐             │
└──────────────────────┘

La lógica subyacente de React sigue siendo igual de sencilla:

return loading
  ? <ProductSkeleton />
  : <ProductCard />;

La única diferencia real aquí es la mejora que aporta a la experiencia del usuario.

6. Estados de error

No siempre todo funciona sin problemas con las llamadas a la API.

La conexión puede interrumpirse.

El servidor puede colapsar.

Una solicitud puede agotar su tiempo de espera.

En lugar de dejar que tu aplicación se bloquee o colapse, deberías mostrar un estado de error en su lugar.

if (error) {
  return (
    <div>
      <h2>Something went wrong.</h2>
      <button>Try Again</button>
    </div>
  );
}

Manejar los fallos de manera adecuada es una característica distintiva de una interfaz de usuario de calidad profesional.

7. Cargando + Error + Éxito

En la práctica, estos tres estados casi siempre aparecen juntos.

function ProductList({
  loading,
  error,
  products
}) {
      if (loading) {
    return <p>Loading...</p>;
      }
  if (error) {
    return <p>Something went wrong.</p>;
      }
  return <Products products={products} />;
     }

Puedes imaginar el flujo de esta manera:

Request
   │
   ├── Loading → Loader
   │
   ├── Failed → Error
   │
   └── Success → Data

Cuando llegues a las llamadas a la API y al useEffect más adelante en esta serie, verás este mismo patrón aparecer una y otra vez.

8. Estados vacíos

Solo porque una solicitud tenga éxito no significa que haya datos reales para mostrar.

Imagina que un usuario busca algo como:

"React Quantum Pizza Developer"

La llamada a la API se completa sin ningún error.

Pero el resultado podría verse así:

products.length === 0

En lugar de dejar la pantalla en blanco, ofrece al usuario algo significativo que ver.

if (products.length === 0) {
  return (
    <div>
      <h2>No Products Found</h2>
      <p>Try changing your search.</p>
    </div>
  );
}

Los estados vacíos son muy importantes para una experiencia de usuario impecable.

Cargando vs vacío vs error

Los nuevos desarrolladores de React a menudo confunden estos tres casos, pero representan situaciones muy diferentes.

LOADING
Data hasn't arrived yet.

EMPTY
Data arrived, but nothing exists.

ERROR
Something failed.

Una aplicación sólida maneja cada uno de ellos por separado.

9. Múltiples condiciones

A veces, lo que se renderiza depende de más de una condición combinadas entre sí.

Considere este flujo:

Is User Logged In?
        ↓
Is Subscription Active?
        ↓
Is User Admin?
        ↓
Show Admin Dashboard

Es tentador intentar incluir todo esto en un único ternario anidado enorme:

condition1
  ? condition2
    ? condition3
      ? <A />
      : <B />
    : <C />
  : <D />

Se compila sin problemas.

Pero es un verdadero infierno para leerlo.

Un enfoque mejor es separar claramente cada condición.

if (!isLoggedIn) {
  return <Login />;
}

if (!hasSubscription) {
  return <UpgradePlan />;
}

if (isAdmin) {
  return <AdminDashboard />;
}

return <UserDashboard />;

Esta versión es mucho más fácil de seguir.

10. Cláusulas de protección

El patrón que acaba de ver tiene un nombre: cláusulas de protección, también conocidas como devoluciones anticipadas.

En lugar de anidar condiciones una dentro de otra:

if
 └── if
      └── if
           └── UI

habilítese con los casos límite desde el principio y devuelva el resultado de inmediato.

if (loading) return <Loader />;
if (error) return <ErrorPage />;
if (!user) return <Login />;
return <Dashboard />;

El resultado es limpio.

Es legible.

Y es mucho más fácil de depurar.

Piense como un desarrollador de React

Antes de escribir un componente, es útil preguntarse:

“¿Cuáles son todos los estados posibles en los que podría encontrarse esta pantalla?”

En el caso de una página generada mediante una llamada a API, esa lista podría verse así:

Loading
Error
Empty
Success

En un flujo de autenticación, podría verse de esta manera:

Logged Out
Checking Authentication
Logged In
Unauthorized

Definir estos estados antes de comenzar a codificar hace que el componente resultante sea mucho más fácil de comprender.

Errores comunes entre principiantes

Agregar demasiados ternarios anidados

No sacrifique la legibilidad solo para ahorrar unas pocas líneas de código.

Omitir el estado vacío

Que una API devuelva un array vacío no es lo mismo que un error; trátelo como un caso aparte.

Confundir el ocultamiento de la interfaz con la seguridad real

Ocultar algo como:

<DeleteButton />

No impide en absoluto que alguien acceda directamente al endpoint de su API.

La autorización real debe realizarse en el backend.

Dejar que && muestre algo incorrecto

Tenga cuidado con códigos como este:

{items.length && <ProductList />}

Si items.length resulta ser 0, React podría terminar mostrando:

0

directamente en la página.

La versión más segura es:

{items.length > 0 && <ProductList />}

Ahora la condición devuelve un valor booleano real.

Buenas prácticas

La legibilidad siempre debe ser lo primero en el renderizado condicional.

Favor de patrones como:

if (loading) return <Loader />;

en lugar de acumular condiciones dentro de JSX profundamente anidados.

Para los estados que se muestran con frecuencia, trasládelos a componentes reutilizables propios:

<Loader />
<ErrorMessage />
<EmptyState />

A medida que los componentes se vuelven más complejos, separe la lógica de negocio de lo que realmente se muestra.

Y no solo diseñe para el escenario óptimo: planifique para cada estado en el que la interfaz podría encontrarse realmente.

Proyecto mini: Panel de control inteligente

Como práctica, intente crear un panel de control que tenga en cuenta casos como:

User Not Logged In
        ↓
Login ScreenUser

    Logged In
        ↓
Loading Dashboard
        ↓
 ┌──────┴──────┐
Error          Success
 ↓                ↓
Error UI       Data Exists?
               ↙       ↘
             YES        NO
              ↓          ↓
          Dashboard   Empty State

Luego, agregue comportamientos basados en roles encima de él:

Admin
↓
Edit + Delete

User
↓
View Only

Un proyecto como este reúne varios conceptos al mismo tiempo:

  • Props
  • Estado
  • Eventos
  • Renderizado condicional

Así es exactamente como las distintas partes de React comienzan a funcionar juntas una vez que se construye algo real.

Preguntas de entrevista

¿Qué es el renderizado condicional?

Es la práctica de mostrar diferentes interfaces según el estado actual o las condiciones de la aplicación.

¿Cuál es la diferencia entre && y una expresión ternaria?

Utilice && cuando solo desea que algo se muestre en su forma original, sin mostrar nada más. Use una expresión ternaria cuando necesite un resultado distinto tanto para los casos verdaderos como falsos.

¿Qué se considera estado vacío?

Es la interfaz que se muestra cuando una solicitud tiene éxito pero simplemente no hay datos para exhibir.

¿Es suficiente el renderizado basado en roles en el frontend para la seguridad?

No. Es una comodidad de la interfaz; las verificaciones reales de autorización aún deben realizarse en el backend.

¿Cuál es la ventaja de los retornos anticipados?

Reducen el anidamiento, lo que hace que los componentes condicionales sean más fáciles de leer y mantener.

Puntos clave

Hasta ahora, ha abarcado todo el alcance del renderizado condicional en React.

Has visto cómo las aplicaciones del mundo real manejan las vistas con sesión iniciada frente a las sin sesión, restringen pantallas según el rol, controlan funcionalidades mediante permisos detallados, muestran indicadores de carga mientras se cargan los datos, utilizan marcadores temporales en lugar de texto simple, exponen errores cuando fallan las solicitudes, envían mensajes a los usuarios cuando el conjunto de resultados está vacío, combinan varias condiciones en un flujo coherente, simplifican las verificaciones anidadas con cláusulas de protección y aplican los hábitos generales que mantienen esta lógica mantenible en un código real.

El renderizado condicional es lo que permite a tu aplicación mostrar la experiencia adecuada para la situación en la que se encuentra actualmente.

Pero aún queda un desafío por delante.

Supongamos que una API devuelve:

1,000 Products

¿Escribirías realmente:

<Product />
<Product />
<Product />
...

mil veces por separado?

Obviamente no.

React tiene una forma mucho mejor de manejar esto.

En Parte 9A, aprenderás cómo renderizar listas utilizando map(), y verás cómo un único componente puede generar cientos o miles de elementos de interfaz directamente a partir de tus datos.

Inmediatamente después, abordarás una de las preguntas más clásicas en las entrevistas sobre React:

¿Por qué React requiere una key?

Nos vemos en Parte 9A — Renderizado de listas y claves en React.

Lecturas relacionadas

  • Construyendo un modelo mental para React: Reconciliación, estado y hooks — Aprende el razonamiento detrás de los conceptos fundamentales de React: reconciliación, componentes, props, estado y hooks, para desarrollar intuición en lugar de memorizar APIs.
  • React Components 101: Construyendo elementos UI reutilizables y mantenibles — Entiende por qué dividir la interfaz de usuario en pequeños componentes React mejora su reutilización, legibilidad y colaboración en equipo, y luego crea tu primer componente funcional.
  • Habilitar el soporte offline en aplicaciones web con Service Workers — Aprenda cómo utilizar Service Workers y la API Cache para que un sitio web se cargue al instante y siga funcionando incluso sin conexión a Internet.
  • La verdadera complejidad del JavaScript moderno proviene de las herramientas, no del idioma — Este artículo explica cómo las características clave de JavaScript como async/await y el encadenamiento opcional simplifican el código, mientras que herramientas y dependencias excesivas generan una complejidad innecesaria.
  • Desarrollo con React y IA: Fuerzas reales, límites reales — Explica en qué aspectos los asistentes de codificación basados en IA realmente aceleran el trabajo con React, dónde fallan, y un flujo de trabajo práctico para utilizarlos sin perder la calidad del código.