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).
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.
Si anteriormente has trabajado con esta librería, este flujo de trabajo se te hará familiar:
import React, { useEffect, useState } from'react'const MyComponent = () => {
const [data, setData] = useState()
useEffect(() => {
}, [])
...
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))
}, [])
...
⚠️ Zona de peligro⚠️
Recuerda que no puedes presentar la información con un simplereturn
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.
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:
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
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
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)
};
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:
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.
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:
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
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…
Por cierto, interesante post…muchas gracias.
excelente
EXCELENTE COMPARTIR DUDAS, GRACIAS
excelente curso, me super sorprendió lo que se puede hacer, sin duda tomare los cursos, GRACIAS PLATZI.