Problemas comunes con React Suspense y cómo corregirlos

Daniel Gonzalez
Beek Tech
Published in
4 min readDec 13, 2018

Con la llegada de React v16.6.0 se integró a su API la posibilidad de hacer code-splitting de forma nativa y sin mayor esfuerzo. Para ello solo es necesario envolver el código para importar el componente de forma dinámica llamando a la función React.lazy(). A continuación el ejemplo de la documentación oficial de React:

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

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}

Tan sencillo como se ve, encontré la manera de toparme con algunos problemas al ocupar esta elegante API y desperdiciar tiempo valioso 🤦🏼‍.

Photo by Annie Spratt on Unsplash

¿Cómo hicimos el code splitting con React Suspense?

Al trabajar en la nueva versión de la landing page de Beek decidí probar esta nueva funcionalidad de React. El componente principal tenía la siguiente estructura:

import {Component, Fragment} from 'react';import BeekValueProposition from './BeekValueProposition';
import BeekSubscriptionBenefits from './BeekSubscriptionBenefits';
import BeekSubscriptionFeatures from './BeekSubscriptionFeatures';
import BeekAudiobooksCatalog from './BeekAudiobooksCatalog';
import BeekApps from './BeekApps';
export default class BeekLandingPage extends React.Component {
render() {
return (
<Fragment>
<BeekValueProposition />
<BeekSubscriptionBenefits />
<BeekSubscriptionFeatures />
<BeekAudiobooksCatalog />
<BeekApps />
</Fragment>
);
}
}

Cada componente es independiente y decidí separar el código de cada uno de los componentes de la siguiente forma:

import {Component, Fragment, lazy, Suspense} from 'react';const BeekValueProposition = lazy(
() => import('./BeekValueProposition')
);
const BeekSubscriptionBenefits = lazy(
() => import('./BeekSubscriptionBenefits')
);
const BeekSubscriptionFeatures = lazy(
() => import('./BeekSubscriptionFeatures')
);
const BeekAudiobooksCatalog = lazy(
() => import('./BeekAudiobooksCatalog')
);
const BeekApps = lazy(
() => import('./BeekApps')
);
export default class BeekLandingPage extends React.Component {
render() {
return (
<Fragment>
<Suspense fallback={ <div /> }>
<BeekValueProposition />
</Suspense>
<Suspense fallback={ <div /> }>
<BeekSubscriptionBenefits />
</Suspense>
<Suspense fallback={ <div /> }>
<BeekSubscriptionFeatures />
</Suspense>
<Suspense fallback={ <div /> }>
<BeekAudiobooksCatalog />
</Suspense>
<Suspense fallback={ <div /> }>
<BeekApps />
</Suspense>
</Fragment>
);
}
}

El código es un poco más verboso, pero funciona como una perla.

Problema #1: No se puede controlar el orden en el que se visualizarán los componentes

La manera en la que React separa el código es que a partir del script principal hace llamadas asíncronas para traer los demás componentes y desplegarlos en la pantalla.

Esto es así por diseño de la API y considero que ese es el comportamiento ideal. Sin embargo, el componente que debía ocupar la parte superior de la pantalla (<BeekValueProposition />) tiene un tiempo de carga más lento que los demás, y era el último que se mostraba.

Entonces opté por no separar el código de este componente y dejarlo en el chunk principal. Cambiando el código de:

<Suspense fallback={ <div /> }>
<BeekValueProposition />
</Suspense>

a

<BeekValueProposition />

¡Perfecto! El primer problema se arregló y el contenido above the fold era el primero en renderizarse 🎊.

Problema #2: La consola del navegador arroja el mensaje: “An update was suspended, but no placeholder UI was provided”

La página funcionaba correctamente y cargaba primero la parte que queríamos, pero empezó a aparecer un mensaje de error poco documentado:

Mensaje de error críptico de React Suspense

En cuanto apareció ese error, no logré ver en ningún lugar que significaba el mensaje. Ni en la documentación oficial de React, ni en los issues del repo de React, ni en Stack Overflow logré encontrar la solución.

Afortunadamente, fue cuestión de un par de horas y probar algunas cosas para detectar cuál fue mi error. Quité en la función render el uso de esta API para el componente, sin embargo, no cambié la forma de importar ese componente.

Al encontrar el problema actualicé el fragmento de código de:

const BeekValueProposition = lazy(
() => import('./BeekValueProposition')
);

a

import BeekValueProposition from './BeekValueProposition';

De esa forma conseguí hacer que la página funcionara de la forma esperada y sin arrojar mensajes de error a la consola.

Adicionalmente, hubo una feliz consecuencia con este cambio 🥳🥳🥳

Auditoría de Lighthouse antes de corregir el bug
Auditoría de Lighthouse después de corregir el bug

Problema #3: Aún no está disponible la API para componentes que hacen peticiones asíncronas en la obtención de datos

Desafortunadamente no hay cómo darle la vuelta para ocupar algún componente de Relay o Apollo. El lado positivo de esto es que ya está próximo a llegar a React.

De acuerdo al Roadmap de React 16.x se espera la funcionalidad de Suspense for Data Fetching a mediados del 2019.

Si eres diseñador, programador, analista o científico de datos y quieres unirte a un increíble equipo de producto donde podrás trabajar con mentores top de Silicon Valley, escríbenos a jobs@beek.io. Siempre estamos buscando personas talentosas.

Más sobre la cultura de trabajo en Beek aquí: https://jobs.beek.io/

--

--