# Todo lo que sé de SWR

## Conceptos Básicos

### Introducción

En este artículo muy largo vamos a ver como usar esta librería para trabajar con data remota, como la que traemos de un API, sacándo el máximo provecho a la librería.

Vamos a ir desde un uso básico de SWR donde vamos a aprender a

- Hacer una petición a nuestro API
- Manejar estados de carga
- Manejar errores

Hasta un uso más avanzado, al final vamos a poder:

- Reusar requests al API
- Reportar errores y reintentar requests
- Compartir la lógica para hacer requests
- Aprovechar data anterior para cargar más rápido
- Mantener la UI actualizada con el API
- Soportar paginación normal e infinita
- Precargar data antes de que se necesite
- User React Suspense para data-fetching
- Conectarse con WebSockets para actualizarse en Real-Time
- Implementar actualizaciones optimistas de la UI

**Antes de empezar**, es necesario saber React y Hooks, y como trabajar con data asíncrona. Si entendés que hace este código, o mejor, escribiste código similar, estás listo.

```js
import React from "react";

function Pokemon() {
  const [data, setData] = React.useState(undefined);
  const [error, setError] = React.useState(undefined);

  React.useEffect(
    function getPokemon() {
      fetch("https://pokeapi.co/api/v2/pokemon/")
        .then((res) => res.json())
        .then((data) => setData(data))
        .catch((error) => setError(error));
    },
    [setData, setError]
  );

  if (error) {
    return <p>Something failed: {error.message}</p>;
  }

  if (!data) {
    return <p>Loading Pokémon...</p>;
  }

  return (
    <div>
      {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
    </div>
  );
}
```

### Instalando SWR

Para poder empezar a usar SWR, lo primero que tenemos que hacer es instalarlo en nuestro proyecto.

Para esto, podemos usar Yarn o npm, el que ustedes prefieran, en mi caso voy a usar Yarn.

```sh
$ yarn add swr
```

Con esto tenemos SWR instalado, vamos a verificarlo, podemos revisar en nuestro `package.json` que esté SWR entre nuestras dependencias.

Además vamos a ver que funciona en nuestro código agregando un simple `console.log`.

```js
import useSWR from "swr";
console.log(useSWR);
```

Si todo va bien, nos tiene que mostrar una función en la consola.

Con esto sabemos que SWR se instaló correctamente y estamos listos para empezar a usarlo.

### Request básico

Vamos a hacer nuestra primera petición o request con SWR.

Para esto tenemos que entender que SWR no viene con la funcionalidad de hacer un request HTTP directamente. Para poder hacer eso tenemos que pasarle una función que se encargue de hacer el request a nuestro API.

Para este proyecto vamos a utilizar el [Pokeapi](https://pokeapi.co).

Vamos a hacer el primer request. Primero vamos a llamar a `useSWR` dentro de nuestro componente pasándole la URL del API como primero argumento.

```js
useSWR("https://pokeapi.co/api/v2/pokemon/pikachu");
```

Este argumento es llamado `key` y se usa como identificador de nuestro request, este es usado por SWR para guardar en una cache interna la información que obtuvimos del API, esto le permite a SWR re-usar esa información mientras la revalida por detrás, aunque la primera vez que usemos una key siempre va a hacer el request sin tener información en cache.

SWR va a pasarlo a una función interna llamada `fetcher` que va a usarlo como URL para hacer nuestro request, esta función es posible personalizarla y en la mayoría de casos vamos a querer hacerlo. Veamos como.

La función `fetcher` que viene por defecto en SWR tiene este formato:

```js
function fetcher(key) {
  return fetch(key).then(res => res.json())
}
```

Solo hace el request usando nuestra `key` como URL y lee el resultado como JSON.

Creemos nuestra función `fetcher` propia, a diferencia de la por defecto vamos a recibir parte de la URL y vamos a armar la URL entera dentro de `fetcher`.

```js
function fetcher(path) {
  const url = `https://pokeapi.co/api/v2/${path}/`;
  return fetch(url).then((res) => res.json());
}
```

Ahora podemos actualizar SWR para solo pasar el path y además pasar el fetcher como segundo argumento.

```js
useSWR("pokemon/pikachu", fetcher);
```

Ahora que tenemos ambos listos podemos acceder a la `data` que devuelve SWR. Por ahora vamos a mostrar el nombre del Pokémon que hicimos fetch en nuestra aplicación.

```js
const { data } = useSWR("pokemon", fetcher);
return (
  <div>
    {data.results.map((pokemon) => (
      <h2>Hello {pokemon.name}</h2>
    ))}
  </div>
);
```

Cuando revisemos cómo funciona la aplicación vamos a obtener el error `undefined is not an object`, eso es porque `data` es inicialmente `undefined` y falla al intentar acceder a la propiedad results.

Para evitar este problema de forma rápida podemos cambiarlo de esta forma.

```js
const { data } = useSWR("pokemon", fetcher);
return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Con esto ya deberíamos ver la lista de Pokémon en la pantalla.

### Manejando el estado de carga

Sabemos que `data` es inicialmente `undefined`, esto pasa cuando nuestra `key` es usada por primera vez por lo que la cache de SWR todavía no tiene datos que mostrar, esto nos permite usar este `undefined` para detectar si nuestro componente está cargando o no y con base a esto mostrar un estado de carga.

```js
const { data } = useSWR("pokemon", fetcher);

if (!data) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Con esto podemos mostrar algo diferente dependiendo del estado.

Si no ven el mensaje de carga es posible que el request haya terminado muy rápido, en ese caso podemos agregar un `setTimeout` de un segundo antes de hacer el request para que este tarde más.

```js
function fetcher(path) {
  const url = `https://pokeapi.co/api/v2/${path}/`;
  return new Promise((resolve, reject) =>
    setTimeout(() => {
      fetch(url)
        .then((res) => res.json())
        .then(resolve, reject);
    }, 1000)
  );
}
```

Una vez verifiquen que su estado de carga funciona pueden volver a dejar el fetcher como estaba.

Algo que puede pasar, especialmente en conexiones lentas, es que un request tarde demasiado en completarse, aunque podemos seguir mostrando el mensaje de carga el usuario puede llegar a creer que algo falló y la aplicación no se actualizó, para esto SWR nos permite pasarle una función que este va a ejecutar si nuestro request tarda mucho.

Esta opción la pasamos en un tercer argumento de SWR como propiedad de un objeto.

```js
const [isLoadingSlow, setIsLoadingSlow] = React.useState(false);
const { data } = useSWR("pokemon", fetcher, {
  onLoadingSlow(key, config) {
    setIsLoadingSlow(true);
  },
});

if (!data && !isLoadingSlow) {
  return <p>Loading Pokémon...</p>;
}

if (!data && isLoadingSlow) {
  return (
    <p>
      Our server is slower than usual, thanks for your patient while we load the
      Pokémon
    </p>
  );
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

De esta forma podemos tener diferentes mensajes o interfaces de carga dependiendo de si tardó mucho o no. Incluso podríamos decidir no mostrar nada hasta que `isLoadingSlow` sea `true`.

```js
const [isLoadingSlow, setIsLoadingSlow] = React.useState(false);
const { data } = useSWR("pokemon", fetcher, {
  onLoadingSlow(key, config) {
    setIsLoadingSlow(true);
  },
});

React.useEffect(() => {
  if (data && isLoadingSlow) {
    const timer = setTimeout(setIsLoadingSlow, 3000, false);
    return () => clearTimeout(timer);
  }
}, [data, isLoadingSlow]);

if (!data && !isLoadingSlow) {
  return null;
}

if (isLoadingSlow) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Con esto si el request se completa lo suficientemente rápido el usuario no ve una interfaz de carga por unos milisegundos para que rápidamente cargue, en vez de eso esperamos un poco y usando un efecto simulamos unos segundos más de carga para que tampoco se vaya muy rápido. Esto último parece raro pero ayuda a dar una mejor experiencia al usuario.

¿Cuánto tiempo va a esperar SWR antes de ejecutar `onLoadingSlow`? Por defecto es espera tres segundos, pero esto podemos cambiarlo usando la opción `loadingTimeout`.

```js
const [isLoadingSlow, setIsLoadingSlow] = React.useState(false);
const { data } = useSWR("pokemon", fetcher, {
  loadingTimeout: 1000,
  onLoadingSlow(key, config) {
    setIsLoadingSlow(true);
  },
});

React.useEffect(() => {
  if (data && isLoadingSlow) {
    const timer = setTimeout(setIsLoadingSlow, 3000, false);
    return () => clearTimeout(timer);
  }
}, [data, isLoadingSlow]);

if (!data && !isLoadingSlow) {
  return null;
}

if (isLoadingSlow) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Con esto cambiamos nuestro tiempo de espera a un segundo, más de este tiempo y vamos a considerar que tarda en cargar y se va a ejecutar `onLoadingSlow`.

## Manejo de Errores

### Manejando errores

En un mundo ideal todos nuestros request al API van a funcionar siempre y nunca vamos a tener errores.

En el mundo real esto no pasa, y un request puede fallar por muchas razones, un error del servidor, falta de internet, un 404, un acceso no autorizado, etc.

Debido a esto es necesario que nuestros componente manejen correctamente este caso donde falle. Por suerte para nostros SWR viene con varias herramientas para esto. Lo primero que podemos hacer es saber si hubo un error y cual fue, así que vamos a empezar por cambiando nuestro `fetcher` para que falle siempre así podemos probar este caso.

```js
function failingFetcher() {
  throw new Error("This is an error");
}
```

Con esto podemos cambiar nuestro componente para que use este `failingFetcher`.

```js
const { data } = useSWR("pokemon", failingFetcher);

if (!data) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Si intentamos usar nuestra aplicación van a ver que se mantiene por siempre en el estado de carga, esto porque como ocurrió un error `data` se mantuvo como `undefined`. Para acceder al error podemos usar la propiedad `error` que SWR nos devuelve.

```js
const { data, error } = useSWR("pokemon", failingFetcher);
```

Con esto podemos agregar un `console.log(error.message)` y ver el mensaje de error en la consola, van a ver que al igual que con `data` la propiedad `error` es inicialmente `undefined`.

Lo siguiente que tenemos que hacer es mostrar este error de alguna forma al usuario para dejarle saber que algo falló, así que podemos agregar un `if` antes de nuestro estado de carga.

```js
const { data, error } = useSWR("pokemon", failingFetcher);

if (error) {
  return <p>Something failed: {error.message}</p>;
}

if (!data) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Es importante que nuestra condición para saber si hay un error esté antes que el estado de carga, esto es porque como vimos antes `data` es `undefined` si hay un error, si ponemos esa condición primero vamos a ver nuestro mensaje de `Loading Pokémon...` por siempre y nunca vamos a ver el error.

### Configurando los reintentos en caso de error

Muchas veces un error puede ocurrir por problemas temporales, como falta de conexión o un error 500, en estos casos en vez de mostrar el error y quedarnos ahí lo que SWR hace es reintentar el request, esto lo hace volviendo a llamar a nuestro fetcher varias veces después de un tiempo.

Cuanto tiempo pasa entre requests? Es dinámico, va creciendo siguiente con algoritmo conocido como [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff), donde la idea es que cada re-intente tarda más que el anterior.

Todo esto es completamente configurable, lo primero que podemos hacer es desactivarlo usando la opción `shouldRetryOnError`, para pasar opciones a SWR lo hacemos con un objeto como tercer argumento de la función.

```js
useSWR("pokemon", failingFetcher, { shouldRetryOnError: false });
```

Con esto configuramos que no reintente, aunque normalmente lo mejor es dejarlo activado, pero es posible que necesitemos configurar cada cuanto va a reintentar, capaz queremos que sea cada un segundo siempre sin usar el exponential backoff, esto lo podemos cambiar con `onErrorRetry`, esta opción es una función que recibe varios argumentos

- `error` con el error recibido
- `key` con la key usada al hacer el fetch
- `config` las opciones usadas al configurar SWR
- `revalidate` es una función con la que podemos volver a intentar el request
- `{ retryCount }` es cuantas veces ya hemos reintentado

```js
useSWR("pokemon", failingFetcher, {
  onErrorRetry(error, key, config, revalidate, { retryCount }) {
    if (key === "pokdemon/pikachu") return;
    if (retryCount >= 10) return;
    setTimeout(() => revalidate({ retryCount: retryCount + 1 }), 5000);
  },
});
```

Como vemos podemos configurar completamente como funciona, en el ejemplo de arriba no reintamos si la key es `pokemon/pikachu`, tampoco si llegamos a diez reintentos. Por último después de cinco segundos llamamos a `revalidate` incrementando `retryCount` en uno.

También, es posible pasar un `errorRetryCount` y `errorRetryInterval` como opciones para configurar el limite de reintentos o el intervalo.

```js
useSWR("pokemon", failingFetcher, {
  errorRetryCount: 10,
  errorRetryInterval: 5000,
});
```

De esta forma es posible configurar como funciona el reintento sin hacer una función propia.

### Reportando Errores

A veces, es posible que necesitemos hacer algo cuando ocurra un error, capaz usamos un servicio como Sentry para registrar los errores de nuestra aplicación, para esto tenemos dos opciones.

La primera opción es que usemos un efecto para que si hay un error lo reportemos a nuestro servicio.

```js
const { error } = useSWR("pokemon", failingFetcher);

React.useEffect(() => {
  if (!error) return;
  report(error);
}, [error]);
```

La segunda opción es que usemos la opción `onError` que viene con SWR.

```js
useSWR("pokemon", failingFetcher, {
  onError(error, key, config) {
    report(error);
  },
});
```

Con eso no necesitamos crear un efecto extra y SWR se va a encargar de llamar a nuestra función cada vez que falle un request.

## Reusabilidad

### Compartiendo y reusando requests

Una vez nuestra aplicación empiece a crecer, es normal reusar la misma información en varias partes, de hecho es preferible para evitar hacer requests innecesarios. SWR nos ayuda con esto gracias a evitar requests duplicados.

Si usamos varias veces la misma `key` de SWR lo que hace la librería es solo ejecutar un request, hasta que pasen al menos dos segundos. También es configurable, en este caso usando la opción `dedupingInterval`.

```js
useSWR("pokemon", fetcher, { dedupingInterval: 5000 });
```

Con ese cambia cualquier petición dentro de un rango de cinco segundos va a re-usar el resultado del request anterior evitando así requests duplicados e innecesarios.

Adicionalmente si vamos a usar el mismo request muchas veces podemos crear un Hook que configure SWR siempre de la misma forma, veamos como.

```js
function usePokemon() {
  return useSWR("pokemon", fetcher);
}
```

Luego podemos llamar a nuestro Hook de la siguiente forma:

```js
const { data, error } = usePokemon();

if (error) {
  return <p>Something failed: {error.message}</p>;
}

if (!data) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Es decir que ahora podemos llamar todas las veces que necesitemos a `usePokemon` y siempre va a venir correctamente configurado para nuestro caso de uso y con la misma `key` y `fetcher`, asegurándonos que no cambie la `key` por un error y terminemos haciendo otro request.

### Creando indicadores de carga y error

Hasta ahora vimos que para saber si hay un error verificamos que `error` exista y para ver si está cargando verificamos que `error` y `data` no existan. Ya que creamos nuestro propio Hook `usePokemon` podemos cambiar un poco el valor devuelto por el Hook para agregar una propiedad `status` o propiedades `isLoading` o `isError`.

```js
function getStatus({ data, error }) {
  if (error && !data) return "error";
  if (!data) return "loading";
  return "success";
}

function usePokemon() {
  const { data, error } = useSWR("pokemon", fetcher);
  const status = getStatus({ data, error });
  const isLoading = status === "loading";
  const isError = status === "error";
  const isSuccess = status === "success";
  return { isLoading, isError, isSuccess, data, error };
}
```

Como podemos ver, usando `status` es posible conseguir el valor de las constantes booleanas.

Ahora si cambiamos nuestro código podríamos tener algo similar a esto:

```js
const { status, data, error } = usePokemon();

switch (status) {
  case "error":
    return <p>Something failed: {error.message}</p>;
  case "loading":
    return <p>Loading Pokémon...</p>;
  case "success":
  default:
    return (
      <div>
        {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
      </div>
    );
}
```

Lo cual queda un poco más simple, o usando las constantes booleanas, podemos tener algo similar a esto:

```js
const { data, error, isLoading, isError } = usePokemon();

if (isError) {
  return <p>Something failed: {error.message}</p>;
}

if (isLoading) {
  return <p>Loading Pokémon...</p>;
}

return (
  <div>
    {data?.results.map((pokemon) => <h2>Hello {pokemon.name}</h2>) ?? null}
  </div>
);
```

Lo cual, si bien no es más corto, es más fácil de entender a simple vista que está pasando y que significa cada condición.

### Definiendo la data inicial

En algunos casos es muy posible que ya tengamos toda o parte de la data inicial que necesitamos para una key específica.

Esto es muy común si usamos un framework como Next.js que nos permite hacer SSR (Server-Side Rendering) o SSG (Static Site Generation), de forma que obtenemos la data en sus método `getServerSideProps` o `getStaticProps` y luego podemos leerlos desde nuestros componentes.

Otro caso de uso es que en una parte de nuestra aplicación tengamos una lista de elementos y luego tenemos cada elemento de forma individual con su propia key, podríamos usar la data de la lista para ir llenando la del elemento individual.

Para ambos casos SWR nos deja usar la opción `initialData` donde podemos pasar que queremos que nuestro Hook tenga como valor inicial.

```js
useSWR("pokemon", fetcher, {
  initialData: {
    count: 1050,
    next: "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
    previous: null,
    results: [
      { name: "bulbasaur", url: "https://pokeapi.co/api/v2/pokemon/1/" },
    ],
  },
});
```

Con esto nuestro Hook va a empezar usando nuestro initialData, algo importante a tener en cuenta es que al usar esta opción SWR desactiva `revalidateOnMount` por lo que no va a intentar hacer fetch para revalidar la data inicial.

Para forzarlo a que lo haga podemos pasar esta opción como `true`.

```js
useSWR("pokemon", fetcher, {
  revalidateOnMount: true,
  initialData: {
    count: 1050,
    next: "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
    previous: null,
    results: [
      { name: "bulbasaur", url: "https://pokeapi.co/api/v2/pokemon/1/" },
    ],
  },
});
```

Ahora sí, con esto ya hicimos que nuestro Hook `usePokemon` va a iniciar siempre con una lista de solo Bulbasaur, podríamos agregar más fijos, o podríamos pasarlos como parámetros a nuestro Hook personalizado, veamos como.

```js
function getStatus({ data, error }) {
  if (error && !data) return "error";
  if (!data) return "loading";
  return "success";
}

function usePokemon({ initialData } = {}) {
  const { data, error } = useSWR("pokemon", fetcher, { initialData });
  const status = getStatus({ data, error });
  const isLoading = status === "loading";
  const isError = status === "error";
  const isSuccess = status === "success";
  return { isLoading, isError, isSuccess, data, error };
}
```

Ahora podemos pasar el `initialData` cuando lo necesitemos, por ejemplo podríamos leer su valor de `localStorage`.

```js
usePokemon({ initialData: JSON.parse(localStorage.getItem("pokemon")) });
```

Otra cosa a tener en cuenta es que SWR no guarda nuestra data inicial en su cache, por lo que esta data es individual por cada instancia de SWR, si usamos dos veces la misma key necesitamos pasar la data inicial a ambas partes.

## Revalidando Data

### Revalidación automática

En muchos casos SWR va a revalidar la data que tiene almacenada en cache, esto nos ayuda a asegurarnos que tengamos siempre la data actualizado, es esta funcionalidad la que le da el nombre SWR (stale while revalidate) lo que hace que SWR nos de información potencialmente desactualizada mientras revalida por detrás para actualizarla.

Veamos que tipos de revalidaciones hace SWR.

**Al montarse**

Cuando un componente se monta y usa SWR vamos a obtener la información en cache y luego inmediatemente después la librería va a ejecutar nuestro fetcher para revalidarla.

Este comportamiento lo podemos desactivar con la opción `revalidateOnMount`.

```js
useSWR("pokemon", fetcher, { revalidateOnMount: false });
```

\*_Al recuperar foco la aplicación_

Nuestra aplicación corre en un tab del navegador del usuario, esto significa que el usuario puede fácilmente cambiar de tab y no volver a nuestra aplicación por minutes, horas, hasta días.

Y no solo puede cambiar de tab, puede incluso cambiar de ventana a otra aplicación de su computadora y no volver a su navegador en un tiempo (piensen en cuando cambian de su navegador a un editor de codigo).

Incluso puede ocurrir cuando el usuario deja nuestra aplicación abierta y cierra su laptop o suspende su computadora.

Cuando el usuario vuelve a nuestra aplicación web, a tener el foco en el tab y ventana del navegador donde corre, se dispara un evento en el navegador. SWR escucha ese evento y revalida la data que tenga en su cache, para asegurarse de que, si pasó mucho tiempo, nos actualicemos a los últimos cambios.

Este comportamiento lo podemos desactivar con la opción `revalidateOnFocus`.

```js
useSWR("pokemon", fetcher, { revalidateOnFocus: false });
```

**Al reconectarse**

Es muy común que el dispositivo de nuestros usuarios pierda conexión a internet. Especialmente cuando está usando su celular y está en movimiento (en un bus por ejemplo). Si esto pasa la perdida puede ser muy corta o puede ser de varios minutos u horas.

Al igual que con el foco del tab, hay un evento en los navegadores para saber cuando cambia la conectividad del usuario, SWR lo usa para revalidar la cache cuando el usuario recupera conexión, de esta forma si pasó media hora sin internet va a volver a poner al día con la data sin recargar.

Este comportamiento lo podemos desactivar con la opción `revalidateOnReconnect`.

```js
useSWR("pokemon", fetcher, { revalidateOnReconnect: false });
```

### Revalidación manual

También es posible que necesitemos revalidar manualmente nuestra data, esto puede ser muy útil cuando sabemos, por ejemplo luego de que el usuario envíe un formulario.

Para iniciar una revalidación manual SWR nos da dos formas.

**Función mutate global**

La primera es la función `mutate` que podemos importar en cualquier parte de nuestra aplicación de SWR.

```js
import useSWR, { mutate } from "swr";
```

Esta función recibe como primer argumento la `key` de cache que queremos revalidar.

```js
mutate("pokemon");
```

Una vez que termine `mutate` también se van a actualizar automaticamente todos los componentes montados que usen la key de cache que actualizamos.

**Función mutate local**

La otra opción es la función `mutate` que obtenemos de SWR.

```js
const { mutate } = useSWR("pokemon", fetcher);
```

La única diferencia con la función `mutate` global es que esta no necesita la `key`, ya viene configurada para el Hook de SWR que la generó.

Incluso podemos devolver esta función como parte de nuestro Hook personalizado

```js
function getStatus({ data, error }) {
  if (error && !data) return "error";
  if (!data) return "loading";
  return "success";
}

function usePokemon({ initialData } = {}) {
  const { data, error, mutate } = useSWR("pokemon", fetcher, { initialData });
  const status = getStatus({ data, error });
  const isLoading = status === "loading";
  const isError = status === "error";
  const isSuccess = status === "success";
  return { isLoading, isError, isSuccess, data, error, mutate };
}
```

### Polling

En muchas aplicaciones la data que manejamos puede cambiar en cualquier momento, especialmente aplicaciones de trabajo colaborativas como pueden ser Trello, o cuyo contenido es principalmente generado por el usuario, como Twitter.

En estos casos esperar a que ocurra una revalidación puede tomar mucho tiempo, por lo que necesitamos volver nuestra aplicación Real-Time, sin embargo, [trabajar con WebSockets es complicado](https://sergiodxa.com/articles/aplicaciones-real-time-alta-escala), una solución más sencilla sería usar polling a [long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling).

SWR nos permite hacer esto última de forma muy sencilla.

Activando la opción `refreshInterval` cuyo valor por defecto es cero (desactivado) SWR va a empezar a mandar request en el intervalo configurado.

```js
useSWR("pokemon", fetcher, { refreshInterval: 5000 });
```

En el ejemplo de arriba, SWR va a volver a traerse la lista de Pokémon cada cinco segundos y si algo cambió va actualizar la cache y todos los componentes usándo esa `key`.

Inicialmente, SWR va a dejar de hacer polling si el usuario no está viendo el navegador o si está offline, usando las opciones `refreshWhenHidden` y `refreshWhenOffline` podemos cambiar este comportamiento, aunque en general es recomendable dejarlo desactivado.

¿Por qué?

En el primer caso podemos evitar seguir haciendo requests si el usuario no usa nuestra aplicación, es común, especialmente en apps como Twitter o Trello, que se deje el tab abierto mientras se hace otra cosa por lo que sería mejor evitar hacer peticiones innecesarias.

En el segundo caso porque si el usuario está offline no tiene sentido seguir intentado hacer requests.

```js
useSWR("pokemon", fetcher, {
  refreshInterval: 5000,
  refreshWhenHidden: true,
  refreshWhenOffline: true,
});
```

## Mutaciones

### Mutando la data local

Antes vimos que podemos forzar una revalidación manual usando al función `mutate`, ya sea la global o la local. Pero ¿Por qué se llama `mutate` y no `revalidate`? Esto es porque si bien podemos ejecutar `mutate(key)` para generar una revalidación también podemos usar la misma función para modificar directamente la data en cache.

Esto nos sirve mucho si queremos reflejar un cambio que hizo el usuario de inmediato.

```js
mutate("pokemon", {
  count: 1050,
  next: "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
  previous: null,
  results: [{ name: "bulbasaur", url: "https://pokeapi.co/api/v2/pokemon/1/" }],
});
```

Cuando ejecutemos nuestra función `mutate` vamos a reemplazar la data de la key `pokemon` con lo que acabamos de pasar como segundo argumento (si usamos la versión local de `mutate` pasamos la nueva data como primer argumento).

Algo importante a tener en cuenta es que para asegurarse de que la data esté siempre al día SWR va a generar una revalidación luego de actualizar la cache. De esta forma si nuestra data no está persistida en el servidor la cache se va a volver a actualizar con la información correcta.

Podemos desactivar este comportamiento pasando un `false` como tercer argumento (o segundo para la versión local), con esto le decimos que no debe revalidar.

```js
mutate(
  "pokemon",
  {
    count: 1050,
    next: "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
    previous: null,
    results: [
      { name: "bulbasaur", url: "https://pokeapi.co/api/v2/pokemon/1/" },
    ],
  },
  false
);
```

### Mutando la data local con base a la actual

Algunas veces un componente tiene parte de la data que queremos guardar en la cache pero no tiene toda, por lo que reemplazar toda la data en cache no es posible. Para estos casos SWR nos permite pasar una función que va a recibir la data actual de la key que definimos.

```js
mutate("pokemon", function appendPokemon(current) {
  return {
    ...current,
    results: current.results.concat({
      name: "entei",
      url: "https://pokeapi.co/api/v2/pokemon/244/",
    }),
  };
});
```

De esta forma podemos mutar con base a la data actual sin necesitar estar pasando toda la data de un lugar a otro o llamar a nuestro Hook en varias partes. Esto es muy útil cuando tenemos un componente de formulario y no queremos que se vuelva a renderizar cada vez que cambia la data, pero si necesitamos actualizarla.

### Mutando la data local de forma asíncrona

Casi siempre que necesitemos mutar la data local es porque creamos, editamos o borramos algún dato. La forma más común de hacer esto es que primero hagamos nuestro request y luego mutemos.

```js
await fetch("/api/v2/pokemon", { method: "POST", body });
mutate("pokemon", function appendPokemon(current) {
  return {
    ...current,
    results: current.results.concat({
      name: "entei",
      url: "https://pokeapi.co/api/v2/pokemon/244/",
    }),
  };
});
```

SWR nos permite pasar una función asíncrona, al hacerlo `mutate` va a esperar a que esta se complete y usar su resultado como nuevo valor de la cache, por lo que podríamos combinar todo en una sola función.

```js
await mutate("pokemon", async function createPokemon(current) {
  await fetch("/api/v2/pokemon", { method: "POST", body });
  return {
    ...current,
    results: current.results.concat({
      name: "entei",
      url: "https://pokeapi.co/api/v2/pokemon/244/",
    }),
  };
});
```

Si nuestro API nos devuelve la data actualiza (por ejemplo la lista con el nuevo elemento), podemos directamente retornar el resultado del request.

```js
await mutate("pokemon", async function createPokemon() {
  return fetch("/api/v2/pokemon", { method: "POST", body }).then((res) =>
    res.json()
  );
});
```

Incluso podemos ir un paso más adelante y eliminar nuestra función `createPokemon` y pasar directamente la promesa.

```js
await mutate(
  "pokemon",
  fetch("/api/v2/pokemon", { method: "POST", body }).then((res) => res.json())
);
```

Con esto, SWR va a esperar a que la promesa se complete y usar el resultado directamente.

Si, por alguna razón, necesitamos la data que obtenemos del request, `mutate` también nos devuelve la data.

```js
try {
  const data = await mutate(
    "pokemon",
    fetch("/api/v2/pokemon", { method: "POST", body }).then((res) => res.json())
  );
} catch (error) {
  // hacé algo con el error acá
}
```

Si la promesa falla, `mutate` va a lanzar un error y vamos a poder atraparlo usando try/catch o `.catch()`. Esto pasa siempre que llamemos a `mutate`, ya sea que su segundo valor (o primero para la versión local) sea una promesa, una función asíncrona o una función síncrona, siempre vamos a obtener el resultado. Incluso si no pasamos nada y solo hacemos una revalidación vamos a obtener la nueva información de la cache.

## Uso avanzado de las keys

### Pasando argumentos por la key

Hasta ahora, usamos SWR pasándo parte de la URL y nuestro fetcher recibía esto como argumento.

```js
function fetcher(path) {
  const url = `https://pokeapi.co/api/v2/${path}/`;
  return fetch(url).then((res) => res.json());
}
```

Esto nos permite reusar nuestro fetcher con distintos paths sin tener que hacer un fetcher por cada posible URL. Podemos hacerlo porque SWR pasa la key como argumento a nuestro fetcher, pero ¿Qué ocurre si necesitamos más de un argumento? Lo más normal es usar un string con la URL pero no es necesario, ya que podemos usar un array como key. Veamos como funcionaría:

```js
useSWR(["pokemon", 25], pokemonFetcher);
```

¿Qué creés que recibiría `pokemonFetcher` en este caso? La respuesta es que SWR llama a nuestro fetcher pasándo cada elemento del array como un argumento a parte, por lo que la definición sería:

```js
function pokemonFetcher(_, number) {
  const url = `https://pokeapi.co/api/v2/pokemon/${number}`;
  return fetch(url).then((res) => res.json());
}
```

> El `_` como primer argumento es una convención para decir que no usamos el argumento, pero por como funciona el lenguaje necesitamos ponerle un nombre, así que usamos `_`.

Con esto ahora podemos tener las keys `["pokemon", 1]`, `["pokemon", 2]`, `["pokemon", 3]`, etc. y usar `"pokemon"` como prefijo en vez de la URL. Podríamos crear otro fetcher más genérico aún si hacemos:

```js
function entityFetcher(resource, id) {
  const url = `https://pokeapi.co/api/v2/${resource}/${id}`;
  return fetch(url).then((res) => res.json());
}
```

Y ahora podríamos usarlo de esta forma:

```js
useSWR(["pokemon", 25], entityFetcher);
useSWR(["item", 1], entityFetcher);
useSWR(["trainer", 123], entityFetcher);
```

De esta forma el primer valor del array que usamos como key funciona como nombre del recurso y prefijo, y el segundo funciona como ID del recurso cuya entidad queremos traernos del API.

### Requests condicionales

Algunas veces queremos evitar hacer un request hasta que alguna condición se cumpla, esto puede ser útil si usamos SWR para hacer una búsqueda y no queremos hacer requests hasta que el usuario escriba algo en un input. Para esto tenemos dos opciones:

**Crear un componente hijo**

La primera opción es que creemos un componente hijo donde hagos la búsqueda y solo rendericemos ese componente si el usuario escribió algo. Veamos un ejemplo:

```js
function search(_, query) {
  return fetch(`/api/search?query=${query}`).then((res) => res.json());
}

function SearchResults({ query }) {
  const { data, error } = useSWR(["search", query], search);
  if (error) return <p>Something happened :(</p>;
  if (!data) return <p>Searching...</p>;
  return data?.map((item) => <ResultItem key={item.id} {...item} />);
}

function SearchBox() {
  const [searchQuery, setSearchQuery] = React.useState("");

  const handleChange = React.useCallback(
    function handleChange(event) {
      setSearchQuery(event.target.value);
    },
    [setSearchQuery]
  );

  return (
    <div>
      <input value={searchQuery} onChange={handleChange} />
      {searchQuery !== "" ? <SearchResults query={searchQuery} /> : null}
    </div>
  );
}
```

Con esto vamos a renderizar `SearchResults` solo cuando `searchQuery` no esté vacío.

Esta opción está buena si además del request queremos evitar otros efectos o por alguna razón hacer el render es pesado. Esta forma es la que deberías usar siempre que sea posible ya que además te va a dar un mejor performance general de tu app.

**Usando una key condicional**

La segunda opción es que usemos una key condicional. SWR nos permite poner `null` como key para evitar hacer el request, esto significa que podemos definir la key como `null` cuando `searchQuery` está vacío.

```js
function search(key, query) {
  return fetch(`/api/search?query=${query}`).then((res) => res.json());
}

function SearchBox() {
  const [searchQuery, setSearchQuery] = React.useState("");
  const { data, error } = useSWR(
    searchQuery !== "" ? ["search", qusearchQueryery] : null,
    search
  );

  const handleChange = React.useCallback(
    function handleChange(event) {
      setSearchQuery(event.target.value);
    },
    [setSearchQuery]
  );

  return (
    <div>
      <input type="search" value={searchQuery} onChange={handleChange} />
      {searchQuery === "" ? <p>Write something to search</p> : null}
      {error ? <p>Something happened :(</p> : null}
      {!data && searchQuery !== "" ? <p>Searching...</p> : null}
      {data ? data?.map((item) => <ResultItem key={item.id} {...item} />) : null}
    </div>
  );
}
```

Con esto tenemos un solo componente donde podemos manejar tanto el estado `searchQuery` como nuestro request usando SWR. Si `searchQuery` cambia se actualiza el request y si se vuelve a poner en `null` se borra.

Otra cosa es que ahora vamos a tener que identificar cuando `data` es `undefined` porque no hay resultados todavia y cuando es `undefined` porque `searchQuery` está vacío, lo que nos agrega un cuarto estado, teniendo:

- No está buscando
- Buscando (cargando)
- Algo falló
- Hay resultado

### Requests dependientes

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!

## Usando data paginada

### Paginación Normal

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!

### Paginación Infinita

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!

## Contenido Bonus

### Prefetching

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!

### Suspense

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!

### Actualizaciones optimistas de la UI

Una actualización optimista, u Optimistic Update en inglés, signfica que primero vamos a actualizar la UI como si nuestro request fuese un éxito, y luego vamos a hacer el request, en caso de que falle, vamos a dar marcha atrás al cambio y dejarlo en el estado anterior. Este patrón es muy usado en aplicaciones como Twitter o Facebook por ejemplo al dar like a un tweet o publicación, si ocurre un error podemos ver como se elimina nuestra like.

Tené en cuanta que no todas las interacciones de nuestra app se pueden volver optimistas, por ejemplo al crear un nuevo elemento es posible que varios datos se creen en el servidor, como pueden ser el ID o la fecha de creación, en estos casos lo mejor es mostrar una UI de carga y recién actualizar cuando se tenga el resultado del request.

Hasta ahora, vimos como hacer el request y la mutación en serie o hacerlos a la vez, en ambos casos la UI no se va a actualizar hasta que terminemos de hacer el request.

Veamos como implementar este patrón.

Lo primero es que necesitamos poder saber el resultado, si tomamos el siguiente ejemplo de base.

```js
await fetch("/api/v2/pokemon", { method: "POST", body });
mutate("pokemon", function appendPokemon(current) {
  return {
    ...current,
    results: current.results.concat({
      name: "entei",
      url: "https://pokeapi.co/api/v2/pokemon/244/",
    }),
  };
});
```

En ese caso sabemos el Pokémon que hay que agregar, por lo que podemos cambiar el orden y evitar una revalidación.

```js
mutate(
  "pokemon",
  function appendPokemon(current) {
    return {
      ...current,
      results: current.results.concat({
        name: "entei",
        url: "https://pokeapi.co/api/v2/pokemon/244/",
      }),
    };
  },
  false
);
await fetch("/api/v2/pokemon", { method: "POST", body });
```

Con esto acabamos de usar implementar nuestra actualización de forma optimista, cambiamos la UI y hacemos el request, pero todavía nos falta una parte ¿Qué ocurre si falla el request? Debemos volver al estado anterior, para esto tenemos dos opciones.

**Guardar la data actual y reemplazar al fallar**

En este caso, lo que vamos a hacer es obtener la data actual, si no tenemos acceso al Hook de SWR podemos importar la cache de SWR directamente y leer de ahí.

```js
const current = cache.get("pokemon");
```

Luego podemos, en caso de error, mutar nuestra cache con la data vieja que guardamos anteriormente.

```js
const current = cache.get("pokemon");
try {
  mutate(
    "pokemon",
    function appendPokemon(current) {
      return {
        ...current,
        results: current.results.concat({
          name: "entei",
          url: "https://pokeapi.co/api/v2/pokemon/244/",
        }),
      };
    },
    false
  );
  await fetch("/api/v2/pokemon", { method: "POST", body });
} catch {
  mutate("pokemon", current);
}
```

Al final como vemos reemplazamos la cache con lo que estaba antes, y de paso dejamos que SWR revalide la data con el servidor, para estar seguros.

**Revalidar al terminar o en caso de error**

La segunda opción es más sencilla, acá lo que hacemos es generar una revalidación, podemos hacerlo en caso de un error como vemos debajo.

```js
try {
  mutate(
    "pokemon",
    function appendPokemon(current) {
      return {
        ...current,
        results: current.results.concat({
          name: "entei",
          url: "https://pokeapi.co/api/v2/pokemon/244/",
        }),
      };
    },
    false
  );
  await fetch("/api/v2/pokemon", { method: "POST", body });
} catch {
  mutate("pokemon");
}
```

O podemos revalidar siempre, para estar seguros.

```js
mutate(
  "pokemon",
  function appendPokemon(current) {
    return {
      ...current,
      results: current.results.concat({
        name: "entei",
        url: "https://pokeapi.co/api/v2/pokemon/244/",
      }),
    };
  },
  false
);
await fetch("/api/v2/pokemon", { method: "POST", body });
mutate("pokemon");
```

Con todo esto, ya tenemos nuestra actualización optimista de la UI lista y funcionando.

### Real Time con WebSockets

> ¿Te interesa este tema en particular? ¿Dejame saber en [Twitter](https://twitter.com/sergiodxa)!