DEV Community

Cover image for Introduciendo React Query
Lucas Bernalte
Lucas Bernalte

Posted on

Introduciendo React Query

Original: https://lucasbernalte.com/blog/introduciendo-react-query

Buenas a todos!

Os quiero hablar de un descubrimiento de hace unos meses pero que hasta ahora no había podido poner en práctica, y que para mí ha sido un antes y un después a la hora de manejar estado en una aplicación en React: React-Query.

logo

Gracias a una charla de su autor, Tanner Linsley, en el React Summit, me decidí tras un tiempo a echarle un ojo, porque me pareció super interesante, y quería compartir con vosotros un poco mis impresiones y los problemas que me ha solucionado.

TLDR;

  • React Query reduce la complejidad de manejar estados asíncronos.
  • Podrías pensar que es capaz de “conectar” dichos estados y las queries entre sí.
  • Distingue entre queries y mutaciones (acciones que cambian datos en BBDD y hacen las queries obsoletas).
  • Se acabó el gestionar los casos de loadings y error.

Una intro sobre React Query

Siempre me ha parecido que lo que veía sobre gestionar estado en el front no terminaban de resolver los problemas de la asincronía, y requerían de muchos trozos de código iguales, que aunque se podían refactorizar, siempre acababas centrándote en controlar estos estados, y no en lo que de verdad tiene importancia.

Pero qué es React Query?

Pues no es más que una libraría para manejar estado de Backend. React Query puede manejar las peticiones a una API por ti, y puede gestionar cuándo debes actualizar los datos, incluso de forma automática.

Queries y mutaciones

La forma en la que React Query es capaz de identificar cada petición es mediante una key que le indicaremos. Además, distingue entre queries y mutaciones. Las queries las podrá hacer de forma automática, y podrá manejar su estado “fresh” (sus datos están actualizados) o “stale” (sus datos están obsoletos). React Query te proporciona unos hooks para manejarlas, useQuery y algunos más, dependiendo del tipo de query que queramos hacer (podemos hacer paginadas, infinitas...).

Sin embargo, las mutaciones, son aquellas queries que modifican datos en BBDD (por ejemplo un POST, PUT o DELETE en un típico CRUD). Si tenemos una lista de libros que obtenemos con un GET, la edición, adición o borrado de un libro serían mutaciones sobre la lista de libros. Estas mutaciones, no tienen ninguna key al no tener que mantener en caché ningún dato, puesto que son acciones que se realizarían puntualmente. Entonces, el hook useMutation en lugar de recibir la key, recibe directamente la función que realiza la mutación, y una configuración adicional.

Un caso de uso que contiene queries y mutaciones sería el siguiente:

Tenemos una tabla con proyectos en la DB y un CRUD básico en el Backend. Si tenemos en Front un listado y por ejemplo una creación, podríamos tener estas dos queries:

Por un lado la query que se trae los proyectos:

const {data, isLoading, error} = useQuery('GetProjects', getProjects);
Enter fullscreen mode Exit fullscreen mode

El funcionamiento dentro de un componente de React es muy sencillo. React Query por defecto, va a realizar una petición en un componente al montarse cuando esté utilizando un hook como useQuery. Utilizando la query anterior, vemos que nos da un estado de isLoading y al resolverse, nos dará o bien un data o un error. El componente volverá a renderizar cuando cambie uno de estos parámetros y tendremos ese manejo ya controlado de forma automática!

Y por otro lado el método para crear proyectos:

const [createProject] = useMutation(
    service.createProject,
    {
        onSuccess: () => queryCache.invalidateQueries('GetProjects')
    }
);
Enter fullscreen mode Exit fullscreen mode

Podemos vincular el primer parámetro del array que nos devuelve con la acción a realizar (probablemente con algún onClick) y fijaos lo que está pasando. Estamos utilizando una mutation, pasándole la función que va a “mutar” los datos que no controlamos, y luego le pasamos qué hacer en el caso en el que la petición haya ido correctamente en el onSuccess. Y lo que le estamos diciendo en ese onSuccess es que ejecute una función que va a invalidar la query con el nombre ’GetProjects’. Automáticamente si detecta que hay una query invalidada, vuelve a pedir los datos, con lo que se vuelve a repetir el flujo de antes y no haría falta gestionar ese estado de “refresh” tampoco.

Un caso un poco más específico

Pues bien, después de conocer por encima qué serían las queries y qué serían las mutaciones, en mi primera implementación de React Query, vi el caso de uso que tenía delante:

  • Una tabla que muestra datos (una query paginada).
  • Acciones de la tabla a nivel de fila y de toda la tabla (mutaciones sobre los datos).

Qué requisitos debe cumplir nuestra implementación?

  • Debe manejar un estado complejo de la tabla
  • Debe manejar cancelaciones.
  • Debe manejar un dato que se obtiene en la primera request, para enviar en peticiones sucesivas.

El componente de tabla que usamos es un componente propio, que nos hace tener que manejar un estado en el componente que lo usa, para guardar ciertos datos (filtrado, paginación, pageSize).

Además para estas peticiones de tabla, necesitamos un parámetro extra que Backend nos devuelve en la primera petición, y que enviaremos en las peticiones sucesivas. Si este parámetro cambiara, tendremos que enviarlo en la siguiente petición y así (temas de caché).

El primer approach fue utilizar una query paginada, añadiendo a la key la paginación, el pageSize y los filtros. Como puedes crear tus propios hooks con React Query, en principio cada query tendrá su propio hook.

Ahora tenemos que añadir el tema de la cancelación y el manejar un dato, así que decidí crear mi hook para manejar todo eso de forma especial para cada petición que tenga que ver con una tabla:

let myParam;
export function useGetMyTableDataQuery(tableState) {
  // Create a new AbortController instance for this request
  const controller = new AbortController();
  // Get the abortController's signal
  const signal = controller.signal;
  return usePaginatedQuery(
    [Queries.QueryName, tableState.page, tableState.pageSize, tableState.filters],
    () => {
      const promise = service.fetchMyTableData({...tableState, param: myParam}, signal);
      // Cancel the request if React Query calls the `promise.cancel` method
      promise.cancel = () => controller.abort();
      return promise.then((resolvedData) => {
        myParam = resolvedData.myParam;
        return resolvedData;
      });
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

De momento para controlar el tema del parámetro que tenemos que guardar para siguientes peticiones, lo haremos a través de una closure (pregunta de examen), guardando el resultado en myParam.

Las acciones que modifican filas o la tabla completa, no tienen más complejidad que la mostrada en el ejemplo con useMutation anterior. Simplemente invalidan la query, o en algunos casos, varias queries (no importa si invalida algunas que no están en pantalla, puesto que no las va a pedir).


Contenido extra


Si te ha gustado este post, ¡tengo para ti otra buena noticia! Estoy preparando un curso de React Query que subiré a una plataforma de e-learning y me gustaría saber tu opinión. El curso será en inglés, pero si te interesa tenerlo en español, házmelo saber vía email, o directamente por Twitter. Si quieres además contenido que voy encontrándome por mi camino, no olvides suscribirte a mi newsletter!

Un saludo!

Top comments (2)

Collapse
 
ozaytsev86 profile image
Olek

Hola Lucas, muy buena introducción además creo que es lo que más se usa de esta librería ya que normalmente son casos de uso muy típicos. Yo también la he empezado a usar en una app que tengo y la verdad es que es una maravilla, porque no solo te ahorra código si no añade una estructura y forma de hacer las llamadas y como tampoco tiene muchas formas distintas de hacerlas, al final la consistencia es brutal. En mi caso tengo un servicio por cada feature digamos y estoy pensando en quitarlos y hacer la llamada directamente desde la query, lo que supondría también hacer alguna lógica dentro de la query, pero es que los servicios que tengo casi todos tienen nada más que la llamada al típico servicio api que gestiona los errores, success etc.... Qué opinas al respecto?

Collapse
 
lucasbernalte profile image
Lucas Bernalte

👋Hola Olek!
Pues la verdad es que no hay ningún problema en hacer la query directamente. Creo que el meterlo o no, dependerá de esa encapsulación que comentas a la hora de hacer la request. Hacer un wrap en un "servicio" de un fetch concreto + un parseo por defecto puede aumentar la mantenibilidad a futuro (imagina que en un futuro quieres agregar una cabecera, cambiar el parseo por defecto porque Backend devuelve ahora los datos encapsulados, etc).

En principio si tienes un servicio ya funcionando, la idea que implica menos esfuerzo sería simplemente utilizar esas funciones dentro de las queries/mutaciones. Además, si el día de mañana quieres utilizar una solución diferente, sigues teniendo tu servicio funcionando.

Espero que te ayude.
Un saludo!