00:00 / 00:00

Fecha publicación: 20 abr 2021

Introducción

En la mayoría de proyectos, a la hora de pedir datos a una REST API o GraphQL usando simplemente fetch, axios o cualquier otra librería, nos basta con hacer la petición justo cuando se monta el componente y listo.

Sin embargo, si empezamos a rascar, nos podemos encontrar muchos casos adicionales:

  • Quiero que cuando vuelva a navegar a la página me muestre los datos que ya tenía, pero que me pida en paralelo datos nuevos.

  • Quiero que cuando haga foco en un componente me recargue datos.

  • Quiero que si he perdido la conexión con el servidor vuelva a recargar datos.

  • Quiero que se recarguen los datos cada X tiempo.

  • Quiero una forma fácil de manejar errores cuando una petición falle.

Si empezamos a sumar casos, nuestro código puede acabar hecho un Sphaguetti... Gracias a la librería SWR, podemos utilizar el hook useSWR que nos simplifica el código para cubrir ciertos escenarios avanzados y que no se convierta en un dolor.

Manos a la obra

Partimos de un codesandbox eligiendo la plantilla de React la cuál contiene el siguiente código:

import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Para poder utilizar la libreria SWR solamente necesitamos añadirla como dependencia del proyecto:

npm install swr --save

Como vemos, la librería está en una fase temprana de desarrollo (version 0.5.5). El nombre de SWR significa stale-while-revalidate, cuyo nombre viene de HTTP RFC 5861. Ésta, es una popular estrategia de invalidación de la caché del navegador para primero devolver los datos de la caché (stale), mientras realiza la petición a servidor (revalidate) para finalmente devolver los datos actualizados.

El objetivo de este ejemplo, será el de recuperar un listado de personajes de la API pública de Rick and Morty.

Primero vamos a definir la url que vamos a utilizar.

import "./styles.css";

+ const url = "https://rickandmortyapi.com/api/character";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Si miramos en la documentación de esta API pública, podemos observar que cuando hacemos una petición, nos devuelve un objeto de este estilo:

{
  "info": {
    "count": 671,
    "pages": 34,
    "next": "https://rickandmortyapi.com/api/character/?page=2",
    "prev": null
  },
  "results": [
    {
      "id": 1,
      "name": "Rick Sanchez",
      "status": "Alive",
      "species": "Human",
      "type": "",
      "gender": "Male",
      "origin": {
        "name": "Earth",
        "url": "https://rickandmortyapi.com/api/location/1"
      },
      "location": {
        "name": "Earth",
        "url": "https://rickandmortyapi.com/api/location/20"
      },
      "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg",
      "episode": [
        "https://rickandmortyapi.com/api/episode/1",
        "https://rickandmortyapi.com/api/episode/2",
        // ...
      ],
      "url": "https://rickandmortyapi.com/api/character/1",
      "created": "2017-11-04T18:48:46.250Z"
    },
    // ...
  ]
}

En nuestro caso, solamente nos vamos a centrar en el array de results y de cada resultado, solamente vamos a mostrar name, species y image.

Vamos por tanto a utilizar la librería swr para hacer dicha petición. En concreto, esta librería lo que expone es el hook useSWR cuyos parámetros son:

  • url: parámetro requerido para hacer la petición.

  • fetcher: parámetro opcional, es la función que se ejecutará para hacer la petición. Por defecto utiliza el método nativo de Javascript fetch de esta forma: const fetcher =(url) => fetch(url).then(res => res.json()

  • options: parámetro opcional, es un objeto de opciones para el hook donde se puede configurar desde los datos iniciales, tiempos de timeout o intervalo si queremos hacer polling, hasta acciones a realizar en caso de éxito o fallo.

+ import useSWR from "swr";
import "./styles.css";

const url = "https://rickandmortyapi.com/api/character";

export default function App() {
+ const { data } = useSWR(url);
  return (
-   <div className="App">
-     <h1>Hello CodeSandbox</h1>
-     <h2>Start editing to see some magic happen!</h2>
-   </div>
+   <ul>
+     {data?.results?.map((character) => (
+       <li key={character.id}>
+         <h5>{character.name}</h5>
+         <h6>{character.species}</h6>
+         <img src={character.image} alt={character.name} />
+       </li>
+     ))}
+   </ul>
  );
}

Lo bueno de esta librería, es que en el fetcher podemos utilizar cualquier librería o tecnología que necesitemos como Axios o incluso peticiones de GraphQL.

Y ¿qué ocurre si se produce un fallo en la petición? ¿Cómo comprobamos si ha ocurrido cualquier error? Pues además de la propiedad data, nos devuelve otra con el error.

Veamos como podemos utilizarla simulando un error:

import useSWR from "swr";
import "./styles.css";

- const url = "https://rickandmortyapi.com/api/character";
+ const url = "https://fail-api.com";

export default function App() {
- const { data } = useSWR(url);
+ const { data, error } = useSWR(url);

+ if (error) {
+   return <h2>Error to load characters</h2>;
+ }

  return (
    <ul>
      {data?.results?.map((character) => (
        <li key={character.id}>
          <h5>{character.name}</h5>
          <h6>{character.species}</h6>
          <img src={character.image} alt={character.name} />
        </li>
      ))}
    </ul>
  );
}

Además de estas características básicas, la librería está preparada para cubrir algunos escenarios más avanzados, como la Auto Revalidation, es decir, volver a ejecutar la petición automáticamente en los siguientes escenarios:

  • Revalidate on Focus: vuelve a revalidar los datos cuando detecta de nuevo el foco en la página del navegador. Puede ser muy útil si por ejemplo el usuario está cambiando entre las tabs del navegador o incluso refrescar los datos en móviles o tablets que se han puesto en modo sleep y vuelven a desbloquear el dispositivo.

  • Revalidate on Interval: revalida los datos cada X tiempo, según el tiempo indicado. Útil en escenarios donde los datos cambian y hay múltiples dispositivos, usuarios, tabs, etc.

  • Revalidate on Reconnect: puede ser muy útil si queremos revalidar los datos cuando se ha producido una desconexión y volvemos a tener conectividad a internet.

  • Revalidate on Mount: vuelve a revalidar los datos cuando se monta el componente. Si no se proveen datos iniciales, se produce esta primera revalidación. En el caso de que hayan datos iniciales, si habilitamos esta opción, podemos forzar a que se revaliden al inicio.

Las revalidaciones de focus y reconnect están habilitadas por defecto, aunque se puede configurar con el parámetro de opciones que vimos anteriormente.

Vamos a actualizar el ejemplo para comprobar algunas de estas opciones, de tal forma que vamos a añadir unos datos iniciales para ver como se revalidan en los diferentes escenarios.

import useSWR from "swr";
import "./styles.css";

- const url = "https://fail-api.com";
+ const url = "https://rickandmortyapi.com/api/character";

+ const initialData = {
+   results: [
+     {
+       id: 1,
+       name: "Rick Sanchez",
+       species: "Human",
+       image: "https://rickandmortyapi.com/api/character/avatar/1.jpeg"
+     }
+   ]
+ };

export default function App() {
- const { data, error } = useSWR(url);
+ const { data, error } = useSWR(url, { initialData });

  if (error) {
    return <h2>Error to load characters</h2>;
  }

  return (
    <ul>
      {data?.results?.map((character) => (
        <li key={character.id}>
          <h5>{character.name}</h5>
          <h6>{character.species}</h6>
          <img src={character.image} alt={character.name} />
        </li>
      ))}
    </ul>
  );
}

Como podemos comprobar, cuando se monta por primera vez el componente, se muestran los datos iniciales. Y si hacemos el foco de nuevo en la ventana, se ejecuta la petición a servidor y se revalidan los datos.

Si por ejemplo queremos forzar que la primera vez haga la revalidación de los datos, simplemente tenemos que activar la opción revalidateOnMount.

...
export default function App() {
- const { data, error } = useSWR(url, { initialData });
+ const { data, error } = useSWR(url, { initialData, revalidateOnMount: true });

  if (error) {
    return <h2>Error to load characters</h2>;
  }

  return (
    <ul>
      {data?.results?.map((character) => (
        <li key={character.id}>
          <h5>{character.name}</h5>
          <h6>{character.species}</h6>
          <img src={character.image} alt={character.name} />
        </li>
      ))}
    </ul>
  );
}

¿Te apuntas a nuestro máster?

Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.