React - Suspense

Suspense permet a un component pausar el renderitzat mentre espera que finalitzi un procés asíncron.

Introducció

Suspense is a built-in feature that simplifies managing asynchronous operations managing what is displayed while your components wait for asynchronous tasks to complete.

import { Suspense } from 'react
<Suspense fallback={<DashboardFallback />}>
<Dashboard />
</Suspense>

When React encounters the Suspense component, it checks if any child components are waiting for a promise to resolve.

If so, React “suspends” the rendering of those components and displays the fallback UI until the promise is resolved.

The fallback UI can be a paragraph, a component, or anything you prefer.

Data fetching

One of the primary use cases of Suspense is managing data fetching in your applications.

Using Suspense, you can display a loading state while fetching data from an API, providing a smoother user experience.

For example, the code below shows how the DataView fetches data and displays a loading message until the data is available.

const fetchData = () => {
let data;
let promise = fetch('/api/data')
.then(response => response.json())
.then(json => { data = json });
return {
read() {
if (!data) {
throw promise;
}
return data;
}
};
};
const resource = fetchData();
const App = () => (
<Suspense fallback={<p>Loading data...</p>}>
<DataComponent />
</Suspense>
);
const DataView = () => {
const data = resource.read();
return (
<div>
<h1>Data: {data.value}</h1>
</div>
);
};

The fetchData function initiates a fetch request and returns an object with a read method. If the data isn’t ready, the read method throws a promise, which tells Suspense to display the fallback UI (“Loading data…”).

Once available, read returns the data, and DataComponent renders it.

Dades

Quan un component necessita mostrar unes dades que ha de proporiconar un servidor, el component no ha de parar el rederitzar de la página, sinó que ha de mostrar alguna cosa fins que obtingui les dades i pugui mostrar el contingut corresponent.

En aquests casos utilitza el component Suspense:

import { Suspense } from 'react
<Suspense fallback={<DataViewFallback />}>
<DataView />
</Suspense>

En el moment de renderitzar la pàgina, si el component <Data/> no està llest, React mostrarà el component <DataFallback/> en el seu lloc.

Aplicació

lazy()

React té un mecanisme d’importació dinàmica anomenat lazy() que et permet carregar un component només quan sigui necessari.

D’aquesta manera l’aplicació es pot dividir en scripts diferents, i l’script inicial és molt més petit i ràpid de carregar.

La funció lazy() accepta una funció que importa el component;

import { Suspense, lazy } from 'react
const DataView = lazy(() => import('./DataView'))

El component <DataView/> es carregarà de manera mandrosa quan sigui necessari. La declaració dinàmica import() especifica la ruta al component que vols importar.

A continuació, encapsula l’element que vols carregar de manera mandrosa en un element Suspense. Pots designar un component de reserva per mostrar mentre el component de càrrega mandrosa s’està recuperant utilitzant el component Suspense.

function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<TestComponent />
</Suspense>
</div>
)
}

Aquí, mentre es recupera el <TestComponent/>, es mostrarà el component de reserva, indicant que el contingut s’està carregant.

Avantatges de React.lazy()

  • Velocitat millorada: Carregant selectivament els components necessaris per a la vista actual i no carregant tots els components alhora, la càrrega mandrosa de components pot millorar la velocitat de l’aplicació.

  • Millor Experiència d’Usuari: Pots millorar l’experiència d’usuari informant als usuaris que l’aplicació està carregant activament material utilitzant Suspense per mostrar un indicador de càrrega.

  • Divisió de Codi: Un dels principals avantatges de lazy() és que fa possible la divisió de codi. El procés de divisió de codi implica dividir el codi de la teva aplicació en paquets més petits, sota demanda. Això minimitza la mida inicial del paquet i accelera el temps de càrrega de la teva aplicació.

Amb lazy(), pots fer divisió de codi i càrrega mandrosa de components en les teves aplicacions React. Aquesta és una característica excel·lent. És una eina útil per optimitzar l’eficiència i els temps de càrrega de les teves aplicacions web, millorant l’experiència d’usuari carregant components només quan són necessaris.

Suspense per a la Recuperació de Dades

A partir de React 18, Suspense també es pot utilitzar per a la recuperació de dades. Això permet als components “suspendre” el renderitzat mentre esperen que les dades es carreguin, mostrant un estat de càrrega mentre tant.

Exemple Bàsic de Recuperació de Dades

Per utilitzar Suspense amb recuperació de dades, necessitem una biblioteca compatible amb Suspense o crear la nostra pròpia implementació. Aquí hi ha un exemple utilitzant una funció personalitzada:

// Funció d'ajuda per a la recuperació de dades compatible amb Suspense
function fetchData(url) {
let status = 'pending';
let result;
let suspender = fetch(url)
.then(response => response.json())
.then(data => {
status = 'success';
result = data;
})
.catch(error => {
status = 'error';
result = error;
});
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
}
};
}
// Recurs de dades
const resource = fetchData('https://api.example.com/data');
// Component que utilitza les dades
function DataComponent() {
const data = resource.read();
return (
<div>
<h2>Dades Carregades</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
// Component principal que utilitza Suspense
function App() {
return (
<div>
<h1>La Meva Aplicació</h1>
<Suspense fallback={<div>Carregant dades...</div>}>
<DataComponent />
</Suspense>
</div>
);
}

En aquest exemple, quan DataComponent intenta llegir les dades, si encara s’estan carregant, es “suspèn” i React mostra el fallback fins que les dades estiguin disponibles.

Biblioteques Compatibles amb Suspense

Algunes biblioteques populars que ofereixen integració amb Suspense per a la recuperació de dades són:

  • React Query: Ofereix suport per a Suspense a través de l’opció suspense: true.
  • SWR: Proporciona l’opció suspense per a la integració amb Suspense.
  • Relay: Dissenyat específicament per treballar amb Suspense des del principi.

Gestió d’Errors amb ErrorBoundary

Quan treballem amb Suspense, especialment per a la recuperació de dades, és important gestionar els errors que puguin sorgir. React proporciona el concepte d’Error Boundaries per capturar errors en qualsevol part de l’arbre de components.

Creant un Error Boundary

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Pots registrar l'error a un servei d'informes d'errors
console.error("Error capturado:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h2>Alguna cosa ha anat malament.</h2>;
}
return this.props.children;
}
}

Utilitzant Error Boundary amb Suspense

function App() {
return (
<div>
<h1>La Meva Aplicació</h1>
<ErrorBoundary fallback={<div>Error en carregar les dades!</div>}>
<Suspense fallback={<div>Carregant dades...</div>}>
<DataComponent />
</Suspense>
</ErrorBoundary>
</div>
);
}

Amb aquesta configuració, si hi ha un error durant la càrrega de dades, l’Error Boundary el capturarà i mostrarà el fallback d’error en lloc de trencar tota l’aplicació.

Suspense i Transicions

React 18 va introduir el concepte de transicions, que funciona molt bé amb Suspense. Les transicions permeten marcar actualitzacions com a no urgents, cosa que permet que el navegador mantingui la capacitat de resposta durant actualitzacions grans.

import { Suspense, useState, useTransition } from 'react';
function App() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<TabButton
isActive={tab === 'home'}
onClick={() => selectTab('home')}
>
Home
</TabButton>
<TabButton
isActive={tab === 'posts'}
onClick={() => selectTab('posts')}
>
Posts (Carrega lentament)
</TabButton>
{isPending && <div>Carregant...</div>}
<Suspense fallback={<h2>Carregant contingut de la pestanya...</h2>}>
{tab === 'home' && <HomeTab />}
{tab === 'posts' && <PostsTab />}
</Suspense>
</div>
);
}

En aquest exemple, quan l’usuari canvia a la pestanya “Posts”, la transició permet que la interfície d’usuari segueixi sent receptiva mentre es carreguen les dades necessàries.

Millors Pràctiques

Quan treballis amb Suspense, considera aquestes millors pràctiques:

  1. Utilitza Error Boundaries: Sempre embolcalla els components Suspense amb Error Boundaries per gestionar errors de manera elegant.

  2. Granularitat Adequada: Col·loca els components Suspense al nivell adequat de l’arbre de components. Massa alt i tota la pàgina esperarà; massa baix i podries tenir múltiples indicadors de càrrega.

  3. Fallbacks Significatius: Dissenya fallbacks que proporcionin context sobre el que s’està carregant i, si és possible, que tinguin dimensions similars al contingut final per evitar salts de disseny.

  4. Utilitza Transicions: Per a actualitzacions no crítiques, utilitza transicions per mantenir la interfície d’usuari receptiva.

  5. Precarrega Dades: Considera precarregar dades per a rutes o components que és probable que l’usuari necessiti aviat.

TODO