Domina React Suspense con patrones pr谩cticos para una obtenci贸n de datos eficiente, estados de carga y manejo de errores robusto. Construye experiencias de usuario m谩s fluidas y resistentes.
Patrones de React Suspense: Obtenci贸n de datos y l铆mites de error
React Suspense es una caracter铆stica poderosa que te permite "suspender" la renderizaci贸n de componentes mientras esperas que se completen las operaciones as铆ncronas, como la obtenci贸n de datos. Combinado con los L铆mites de Error, proporciona un mecanismo robusto para manejar estados de carga y errores, lo que resulta en una experiencia de usuario m谩s fluida y resistente. Este art铆culo explora varios patrones para aprovechar Suspense y los L铆mites de Error de manera efectiva en tus aplicaciones React.
Comprendiendo React Suspense
En esencia, Suspense es un mecanismo que permite a React esperar algo antes de renderizar un componente. Este "algo" es t铆picamente una operaci贸n as铆ncrona, como obtener datos de una API. En lugar de mostrar una pantalla en blanco o un estado intermedio potencialmente enga帽oso, puedes mostrar una interfaz de usuario de respaldo (por ejemplo, un spinner de carga) mientras se cargan los datos.
El beneficio clave es la mejora del rendimiento percibido y una experiencia de usuario m谩s agradable. Los usuarios se presentan inmediatamente con retroalimentaci贸n visual que indica que algo est谩 sucediendo, en lugar de preguntarse si la aplicaci贸n est谩 congelada.
Conceptos clave
- Componente Suspense: El componente
<Suspense>envuelve los componentes que podr铆an suspenderse. Acepta una propiedadfallback, que especifica la interfaz de usuario a renderizar mientras los componentes envueltos est谩n suspendidos. - Interfaz de usuario de respaldo: Esta es la interfaz de usuario que se muestra mientras la operaci贸n as铆ncrona est谩 en curso. Puede ser cualquier cosa, desde un simple spinner de carga hasta una animaci贸n m谩s elaborada.
- Integraci贸n de promesas: Suspense funciona con promesas. Cuando un componente intenta leer un valor de una Promesa que a煤n no se ha resuelto, React suspende el componente y muestra la interfaz de usuario de respaldo.
- Fuentes de datos: Suspense se basa en fuentes de datos que son compatibles con Suspense. Estas fuentes exponen una API que permite a React detectar cu谩ndo se est谩n obteniendo datos.
Obtenci贸n de datos con Suspense
Para usar Suspense para la obtenci贸n de datos, necesitar谩s una biblioteca de obtenci贸n de datos compatible con Suspense. Aqu铆 tienes un enfoque com煤n que utiliza una funci贸n fetchData personalizada:
Ejemplo: Obtenci贸n de datos simple
Primero, crea una funci贸n de utilidad para obtener datos. Esta funci贸n debe manejar el aspecto de 'suspensi贸n'. Envolveremos nuestras llamadas fetch en un recurso personalizado para manejar correctamente el estado de la promesa.
// utils/api.js
const wrapPromise = (promise) => {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
}
return result;
},
};
};
const fetchData = (url) => {
const promise = fetch(url)
.then((res) => res.json())
.then((data) => data);
return wrapPromise(promise);
};
export default fetchData;
Ahora, creemos un componente que utiliza Suspense para mostrar datos de usuario:
// components/UserProfile.js
import React from 'react';
import fetchData from '../utils/api';
const resource = fetchData('https://jsonplaceholder.typicode.com/users/1');
function UserProfile() {
const user = resource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Tel茅fono: {user.phone}</p>
</div>
);
}
export default UserProfile;
Finalmente, envuelve el componente UserProfile con <Suspense>:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
function App() {
return (
<Suspense fallback={<p>Cargando datos del usuario...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
En este ejemplo, el componente UserProfile intenta leer los datos del usuario del recurso. Si los datos a煤n no est谩n disponibles (la Promesa a煤n est谩 pendiente), el componente se suspende y se muestra la interfaz de usuario de respaldo ("Cargando datos del usuario..."). Una vez que se obtienen los datos, el componente se vuelve a renderizar con la informaci贸n real del usuario.
Beneficios de este enfoque
- Obtenci贸n de datos declarativa: El componente expresa *qu茅* datos necesita, no *c贸mo* obtenerlos.
- Gesti贸n centralizada del estado de carga: El componente Suspense maneja el estado de carga, simplificando la l贸gica del componente.
L铆mites de error para la resiliencia
Si bien Suspense maneja los estados de carga con elegancia, no maneja inherentemente los errores que pueden ocurrir durante la obtenci贸n de datos o la renderizaci贸n de componentes. Ah铆 es donde entran los L铆mites de Error.
Los L铆mites de Error son componentes de React que capturan los errores de JavaScript en cualquier lugar del 谩rbol de componentes secundarios, registran esos errores y muestran una interfaz de usuario de respaldo en lugar de bloquear toda la aplicaci贸n. Son cr铆ticos para construir interfaces de usuario resilientes que puedan manejar con gracia los errores inesperados.
Creando un l铆mite de error
Para crear un L铆mite de Error, debes definir un componente de clase que implemente los m茅todos de ciclo de vida static getDerivedStateFromError() y componentDidCatch().
// components/ErrorBoundary.js
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
// Actualiza el estado para que el siguiente renderizado muestre la interfaz de usuario de respaldo.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, errorInfo) {
// Tambi茅n puedes registrar el error en un servicio de informes de errores
console.error("Error capturado: ", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// Puedes renderizar cualquier interfaz de usuario de respaldo personalizada
return (
<div>
<h2>Algo sali贸 mal.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
El m茅todo getDerivedStateFromError se llama cuando se lanza un error en un componente descendiente. Actualiza el estado para indicar que ha ocurrido un error.
El m茅todo componentDidCatch se llama despu茅s de que se ha lanzado un error. Recibe el error y la informaci贸n del error, que puedes usar para registrar el error en un servicio de informes de errores o mostrar un mensaje de error m谩s informativo.
Usando l铆mites de error con Suspense
Para combinar L铆mites de Error con Suspense, simplemente envuelve el componente <Suspense> con un componente <ErrorBoundary>:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
import ErrorBoundary from './components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<p>Cargando datos del usuario...</p>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}
export default App;
Ahora, si ocurre un error durante la obtenci贸n de datos o la renderizaci贸n del componente UserProfile, el L铆mite de Error capturar谩 el error y mostrar谩 la interfaz de usuario de respaldo, evitando que toda la aplicaci贸n se bloquee.
Patrones avanzados de Suspense
M谩s all谩 de la obtenci贸n b谩sica de datos y el manejo de errores, Suspense ofrece varios patrones avanzados para construir interfaces de usuario m谩s sofisticadas.
Divisi贸n de c贸digo con Suspense
La divisi贸n de c贸digo es el proceso de dividir tu aplicaci贸n en trozos m谩s peque帽os que se pueden cargar bajo demanda. Esto puede mejorar significativamente el tiempo de carga inicial de tu aplicaci贸n.
React.lazy y Suspense facilitan incre铆blemente la divisi贸n de c贸digo. Puedes usar React.lazy para importar componentes din谩micamente y luego envolverlos con <Suspense> para mostrar una interfaz de usuario de respaldo mientras se cargan los componentes.
// components/MyComponent.js
import React from 'react';
const MyComponent = React.lazy(() => import('./AnotherComponent'));
function App() {
return (
<Suspense fallback={<p>Cargando componente...</p>}>
<MyComponent />
</Suspense>
);
}
export default App;
En este ejemplo, el MyComponent se carga bajo demanda. Mientras se est谩 cargando, se muestra la interfaz de usuario de respaldo ("Cargando componente..."). Una vez que se carga el componente, se renderiza normalmente.
Obtenci贸n de datos en paralelo
Suspense te permite obtener m煤ltiples fuentes de datos en paralelo y mostrar una 煤nica interfaz de usuario de respaldo mientras se cargan todos los datos. Esto puede ser 煤til cuando necesitas obtener datos de m煤ltiples API para renderizar un solo componente.
import React, { Suspense } from 'react';
import fetchData from './api';
const userResource = fetchData('https://jsonplaceholder.typicode.com/users/1');
const postsResource = fetchData('https://jsonplaceholder.typicode.com/posts?userId=1');
function UserProfile() {
const user = userResource.read();
const posts = postsResource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<h3>Posts:</h3>
<ul>
{posts.map(post => (<li key={post.id}>{post.title}</li>)))}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback={<p>Cargando datos del usuario y publicaciones...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
En este ejemplo, el componente UserProfile obtiene datos de usuario y datos de publicaciones en paralelo. El componente <Suspense> muestra una 煤nica interfaz de usuario de respaldo mientras se cargan ambas fuentes de datos.
API de transici贸n con useTransition
React 18 introdujo el hook useTransition, que mejora Suspense al proporcionar una forma de gestionar las actualizaciones de la interfaz de usuario como transiciones. Esto significa que puedes marcar ciertas actualizaciones de estado como menos urgentes e impedir que bloqueen la interfaz de usuario. Esto es especialmente 煤til cuando se trata de una obtenci贸n de datos m谩s lenta o de operaciones de renderizado complejas, mejorando el rendimiento percibido.
As铆 es como puedes usar useTransition:
import React, { useState, Suspense, useTransition } from 'react';
import fetchData from './api';
const resource = fetchData('https://jsonplaceholder.typicode.com/users/1');
function UserProfile() {
const user = resource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Tel茅fono: {user.phone}</p>
</div>
);
}
function App() {
const [isPending, startTransition] = useTransition();
const [showProfile, setShowProfile] = useState(false);
const handleClick = () => {
startTransition(() => {
setShowProfile(true);
});
};
return (
<div>
<button onClick={handleClick} disabled={isPending}>
Mostrar perfil de usuario
</button>
{isPending && <p>Cargando...</p>}
<Suspense fallback={<p>Cargando datos del usuario...</p>}>
{showProfile && <UserProfile />}
</Suspense>
</div>
);
}
export default App;
En este ejemplo, al hacer clic en el bot贸n "Mostrar perfil de usuario" se inicia una transici贸n. startTransition marca la actualizaci贸n setShowProfile como una transici贸n, lo que permite a React priorizar otras actualizaciones de la interfaz de usuario. El valor isPending de useTransition indica si hay una transici贸n en curso, lo que te permite proporcionar retroalimentaci贸n visual (por ejemplo, deshabilitar el bot贸n y mostrar un mensaje de carga).
Mejores pr谩cticas para usar Suspense y l铆mites de error
- Envuelve Suspense alrededor del 谩rea m谩s peque帽a posible: Evita envolver grandes partes de tu aplicaci贸n con
<Suspense>. En su lugar, envuelve solo los componentes que realmente necesitan suspenderse. Esto minimizar谩 el impacto en el resto de la interfaz de usuario. - Usa interfaces de usuario de respaldo significativas: La interfaz de usuario de respaldo debe proporcionar a los usuarios informaci贸n clara e informativa sobre lo que est谩 sucediendo. Evita los spinners de carga gen茅ricos; en su lugar, intenta proporcionar m谩s contexto (por ejemplo, "Cargando datos del usuario...").
- Coloca los l铆mites de error estrat茅gicamente: Piensa detenidamente d贸nde colocar los l铆mites de error. Col贸calos lo suficientemente alto en el 谩rbol de componentes para capturar errores que puedan afectar a m煤ltiples componentes, pero lo suficientemente bajos para evitar capturar errores que sean espec铆ficos de un solo componente.
- Registra los errores: Usa el m茅todo
componentDidCatchpara registrar los errores en un servicio de informes de errores. Esto te ayudar谩 a identificar y solucionar errores en tu aplicaci贸n. - Proporciona mensajes de error amigables para el usuario: La interfaz de usuario de respaldo que se muestra mediante los L铆mites de Error debe proporcionar a los usuarios informaci贸n 煤til sobre el error y qu茅 pueden hacer al respecto. Evita la jerga t茅cnica; en su lugar, usa un lenguaje claro y conciso.
- Prueba tus l铆mites de error: Aseg煤rate de que tus L铆mites de Error funcionen correctamente lanzando errores deliberadamente en tu aplicaci贸n.
Consideraciones internacionales
Al usar Suspense y L铆mites de Error en aplicaciones internacionales, considera lo siguiente:
- Localizaci贸n: Aseg煤rate de que las interfaces de usuario de respaldo y los mensajes de error est茅n correctamente localizados para cada idioma compatible con tu aplicaci贸n. Usa bibliotecas de internacionalizaci贸n (i18n) como
react-intloi18nextpara administrar las traducciones. - Dise帽os de derecha a izquierda (RTL): Si tu aplicaci贸n admite idiomas RTL (por ejemplo, 谩rabe, hebreo), aseg煤rate de que las interfaces de usuario de respaldo y los mensajes de error se muestren correctamente en dise帽os RTL. Usa propiedades l贸gicas de CSS (por ejemplo,
margin-inline-starten lugar demargin-left) para admitir dise帽os LTR y RTL. - Accesibilidad: Aseg煤rate de que las interfaces de usuario de respaldo y los mensajes de error sean accesibles para los usuarios con discapacidades. Usa atributos ARIA para proporcionar informaci贸n sem谩ntica sobre el estado de carga y los mensajes de error.
- Sensibilidad cultural: Ten en cuenta las diferencias culturales al dise帽ar interfaces de usuario de respaldo y mensajes de error. Evita usar im谩genes o lenguaje que puedan ser ofensivos o inapropiados en ciertas culturas. Por ejemplo, un spinner de carga com煤n podr铆a percibirse negativamente en algunas culturas.
Ejemplo: Mensaje de error localizado
Usando react-intl, puedes crear mensajes de error localizados:
// components/ErrorBoundary.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
class ErrorBoundary extends React.Component {
// ... (igual que antes)
render() {
if (this.state.hasError) {
return (
<div>
<h2><FormattedMessage id="error.title" defaultMessage="Something went wrong." /></h2>
<p><FormattedMessage id="error.message" defaultMessage="Please try again later." /></p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Luego, define las traducciones en tus archivos de configuraci贸n regional:
// locales/en.json
{
"error.title": "Something went wrong.",
"error.message": "Please try again later."
}
// locales/fr.json
{
"error.title": "Quelque chose s'est mal pass茅.",
"error.message": "Veuillez r茅essayer plus tard."
}
Conclusi贸n
React Suspense y los L铆mites de Error son herramientas esenciales para construir interfaces de usuario modernas, resistentes y f谩ciles de usar. Al comprender y aplicar los patrones descritos en este art铆culo, puedes mejorar significativamente el rendimiento percibido y la calidad general de tus aplicaciones React. Recuerda considerar la internacionalizaci贸n y la accesibilidad para garantizar que tus aplicaciones sean utilizables por una audiencia global.
La obtenci贸n de datos as铆ncronos y el manejo adecuado de errores son aspectos cr铆ticos de cualquier aplicaci贸n web. Suspense, combinado con los L铆mites de Error, ofrece una forma declarativa y eficiente de gestionar estas complejidades en React, lo que resulta en una experiencia de usuario m谩s fluida y confiable para usuarios de todo el mundo.