17

¿Qué es React Suspense? ¿Cómo lo integro a mis proyectos?

38273Puntos

hace un mes

Curso de React Avanzado
Curso de React Avanzado

Curso de React Avanzado

Crea aplicaciones móviles en ReactJS. Genera consultas en GraphQL y gestiona usuarios. Implementa Testing básico con Cypress y convierte tus apps en PWA con herramientas como Hooks, React Apollo, Reach Router y JSON Web Tokens.

La React Conf 2021 llegó y tuvimos el esperado anuncio de nuevas características para nuestra librería favorita.

React 18 se encuentra actualmente en beta. Y aunque React Suspense todavía no está listo para aplicaciones en producción, sí que podemos aprender cómo funciona y cómo cambiará nuestra forma de desarrollar aplicaciones, sobre todo en la experiencia de carga (loading experience).

¿Qué cambia React Suspense?

La propia documentación de React explica de manera muy clara lo que es este nuevo componente de la siguiente forma:

Suspense para la carga de datos es una nueva funcionalidad que te permite “esperar” declarativamente por cualquier otra cosa, incluyendo datos, scripts, u otro trabajo asíncrono

Aunque esta labor suene relativamente sencilla de hacer, Suspense nos facilita una forma de requerir datos asíncronos, pero enfocándose en la experiencia de usuario (UX).

Con esto en mente vamos a recordar una de las formas sobre como manejamos el asíncronismo con lo que tenemos actualmente en React.

Asincronismo SIN React Suspense

Si anteriormente has trabajado con esta librería, este flujo de trabajo se te hará familiar:

  • 1️⃣ Crea tu componente con los hooks de estado y efecto
import React, { useEffect, useState } from'react'const MyComponent = () => {
  const [data, setData] = useState()

  useEffect(() => {
  }, [])
...
  • 2️⃣ Obtén datos de cualquier lugar

A manera de ejemplo, consumiré la API de Open Weather para consultar el clima de un lugar aleatorio:

import React, { useEffect, useState } from'react'const MyComponent = () => {
  const [data, setData] = useState()

  useEffect(() => {
    fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=london&units=metric&lang=es&appid=${API_KEY}`
    )
    .then(response => response.json())
    .then(json => setData(json))
  }, [])	
  ...
  • 3️⃣ Presenta la información al usuario

⚠️ Zona de peligro⚠️
Recuerda que no puedes presentar la información con un simple return como el siguiente:

return(
    <div>
      El clima actual en {data.name} es {data.currentWeather}
    div>
  )

Si ejecutamos este código lo más probable es que no funcione porque aún no tienes la información que quieres presentar, así que debes asegurarte que tus usuarios tengan una buena experiencia haciendo un poco de lógica como esta:

return (
    <div>
      {data ? (
        <p>El clima actual en {data.name} es {data.currentWeather}p>
      ) : (
      <p>Obteniendo el clima actual...p>
      )}
    div>
);

Haciendo uso de la sintaxis de ES6, el operador ternario nos ayuda a verificar la existencia del estado en nuestro componente. Así nuestros usuarios siempre sabrán qué está pasando en caso de que sigamos obteniendo nuestros datos o ya los tengamos listos para mostrar.

En mi Codepen puedes ver el código para este componente.

Componente sin suspense

Asincronismo con React Suspense

Al principio de este blogpost te conté las bondades que tiene esta nueva característica de React. Vas a notar muy rápido cómo nos ayuda hacer un código más legible y separar un poco nuestra lógica de los componentes.

Para que tengas un enfoque práctico de Suspense, haremos un componente como el anterior, pero más complejo, ya que usaremos 2 API’s:

  1. El API de OpenWeather para obtener el clima actual
  2. El API de geolocalización del navegador para que obtengas datos precisos sobre el clima en tu ciudad
  • 0️⃣ Crea tu cuenta en OpenWeather

Solo debes ir al enlace que te deje arriba y hacer el proceso de registro, es totalmente gratis. Una vez termines el registro dirígete al apartado de API keys y crea una nueva para usarla en este componente.

⚠️ No compartas tu API Key con nadie

  • 1️⃣ Construye tu recurso

A diferencia del anterior proceso que te mostré, en este paso nos olvidaremos totalmente de los hooks useEffect y useState. Suspense junto con el recurso que haremos nos ayudaran con esa labor.

El recurso del que te cuento será una función que recibe una promesa (aquí nos encargaremos del asincronismo) y retornaremos la función read(), la cual será la responsable de comunicarle a Suspense cuando tengamos la data.

👁 OJO: Este código funciona correctamente, pero está incompleto porque no tiene manejo de errores. ¿Te animas a completarlo?

Te recomiendo que esta lógica la tengas en un archivo separado para que esté más ordenado el desarrollo.

exportconst wrapPromise = (promise) => {
  let status = "pending"let result
  let suspender = promise.then(
    (response) => {
      status = "success"
      result = response
    }
  )
  return {
    read() {
      if (status === "pending") {
        throw suspender
      }
      if (status === "success") {
        return result
      }
    }
  }
}

Siéntete libre de editar ese código de manera que te sientas más cómoda o cómodo trabajando

  • 2️⃣ Utiliza el recurso para obtener la información de tus componentes

Para el ejercicio harás 2 promesas, pero es importante recordar que primero llamarás a la API de geolocalización del navegador para así brindar los argumentos necesarios a la API de OpenWeather. De esta forma solo le pasarás la promesa resultante de hacer fetch a nuestra API externa.

Si al igual que yo decides separar el recurso de wrapPromise, no olvides importarlo en el archivo donde manejaras el asincronismo.

import { wrapPromise  } from'../resources/wrapPromise.js'

Para una mejor organización decidí ordenarlo de esta forma:

const promiseWeather = ({ lat, lon }) => {
  // const API_KEY = "usa tu api key aquí"return fetch(
    `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&units=metric&appid=${API_KEY}`
  ).then((response) => response.json())
};

La API de geolocalización no retorna una promesa, por lo que es necesario retornarla explícitamente.

const promiseGps = () => {
  returnnewPromise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition((position) => {
      resolve({
        lat: position.coords.latitude,
        lon: position.coords.longitude,
      })
    })
  })
}

Recuerda exportar la función que utiliza nuestro recurso para manejar la asincronía.

exportconst fetchWeatherData = () => {
  const weather = promiseGps().then((response) => promiseWeather(response))
  return wrapPromise(weather)
};
  • 3️⃣ Crea tu componente contenedor y tu componente que utilizará la data

El siguiente componente va a consumir todo lo anterior, aunque puedes personalizarlo con los estilos que te agraden.

import React from'react';
import { fetchWeatherData  } from'./resources/fetchData'

Toda la data que estoy consumiendo en el componente proviene de la API, puedes consultar la documentación para sacarle más provecho

const weather = fetchWeatherData()

exportconst CurrentWeather = () => {
  const weatherDetails = weather.read()
  return(
    <div><p>The weather in {weather.name} is {weather.weather[0].description}p><div><imgsrc={`https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`} 
	alt={weather.weather[0].description} />div><p>{weather.main.temp} °Cp>div>
  )
}

⚠️ Importante: el llamado de la función asíncrona debe mantenerse afuera del componente

Este componente es necesario para envolver al que consumirá la data con Suspense.

Ya que estás aquí, te explico que le deberás pasar un fallback, el cual será el que verán nuestros usuarios hasta que carguen los datos (en este caso, yo puse un spinner). Si no tuviera algún componente para mostrar o simplemente no quieras mostrar nada, podrás asignarle el valor de null para no ocasionar ningún problema en la carga.

import React, { Suspense } from'react'import { CurrentWeather } from'./components/CurrentWeather'const App = () => {
  return(
    <div><Suspensefallback={<divclassName="spinner">div> }><CurrentWeather />Suspense>div>
  )
}

Con todo esto se puede lograr un componente como el siguiente:

Suspense

Aquí puedes ver el código completo


Tal vez usar Suspense para un solo componente necesita un poco más de tiempo, pero en la creación de WebApps completas da un mejor rendimiento y hace que evites una gran cantidad de código repetido.

¿Qué te parece si lo ponemos en práctica?

Con las API’s que te compartí al inicio del blog, desarrolla una WebApp para consultar el clima en cualquier parte del mundo. Usa Suspense y todas sus bondades para llevar al siguiente nivel el proyecto.

Otro reto que te dejo es que corrijas un poco el código que te compartí para que puedas hacer un manejo de errores para las siguientes situaciones:

  • El usuario deniega el acceso al GPS o no el navegador no tiene esta API
  • La API externa retorna un error

Una vez completes estos retos, compártenos en la sección de comentarios cómo los resolviste y cómo hiciste el proyecto para consultar el clima mediante el GPS.

¡Si tienes alguna duda, también puedes dejarla aquí abajo y trataré de responderte lo más rápido!

Si quieres estudiar esta herramienta a más profundidad, te recomiendo tomar el Curso de React avanzado con nuestro profesor Midudev.

En este curso realizas un proyecto real donde usas Suspense para dar un mejor performance a tu App.

¡Ahora tienes una nueva herramienta como Frontend Developer!

#NuncaParesDeAprender

Curso de React Avanzado
Curso de React Avanzado

Curso de React Avanzado

Crea aplicaciones móviles en ReactJS. Genera consultas en GraphQL y gestiona usuarios. Implementa Testing básico con Cypress y convierte tus apps en PWA con herramientas como Hooks, React Apollo, Reach Router y JSON Web Tokens.
Leonardo de los angeles
Leonardo de los angeles
LeoCode0

38273Puntos

hace un mes

Todas sus entradas
Escribe tu comentario
+ 2
4
8178Puntos

Hola

No comprendi una cosa:

Al crear el componente CurrentWeather, en la constante weather se almacena la promesa, verdad??

Lo que se me hace extrano y no comprendo es que la respuesta se obtiene al ejecutar weather.read() en la constante weatherDetails, pero para acceder a los datos del clima, se esta utilizando “weather .name”…

Es decir weatherDetails no se utiliza en el resto del codigo…

2
8178Puntos
un mes

Por cierto, interesante post…muchas gracias.

3
38273Puntos
un mes

Hola Gabriel!

Lamento la confusión, la constante weatherDetails es la que se debería usar en todo el código. Es un pequeño error pero en el codepen está corregido

Lo correcto sería que estuviese así

<p>The weather in {weatherDetails.name} is {weatherDetails.weather[0].description}p>

Muchas gracias por la observación!

2
11967Puntos
un mes

EXCELENTE COMPARTIR DUDAS, GRACIAS

2
11967Puntos

excelente curso, me super sorprendió lo que se puede hacer, sin duda tomare los cursos, GRACIAS PLATZI.