Controlando el estado de las llamadas a la API con Redux

Cómo realizar llamadas a una API controlando su estado con Redux

Image for post
Image for post
React + Axios + Redux

Lo primero de todo: Sí, podría haber hecho este artículo basándome en la API Fetch, pero el proyecto en el que ahora mismo estoy trabajando ya tenía implementada la librería Axios y me he basado en él para escribir el artículo. Disculpas de antebrazo y, como supondréis, la metodología de la que hablaré a lo largo del artículo es perfectamente extrapolable a Fetch (aquí os dejo el pollyfill de Fetch para generar un AbortController)

Dicho ésto, ¡vamos a ello!

Introducción

Si os ha tocado abordar algún proyecto de cierta complejidad desarrollando con React es probable que hayáis tenido que realizar consultas a un backend y a la API que implementa. Y muy probablemente para gestionar el estado estéis empleando Redux, librería que junto a Flux ha revolucionado el paradigma del manejo del estado de una aplicación. De hecho probablemente esta arquitectura esté presente en un gran porcentaje de las aplicaciones desarrolladas con React dada su versatilidad y eficiencia.

Sin embargo, la gestión del estado síncrona que propone Redux no se lleva de primeras muy bien con cualquier tipo de gestión asíncrona del mismo, por lo que es necesario tirar de middlewares como redux-thunk o redux-saga para conseguir una integración decente entre las llamadas asíncronas y el store de Redux.

Sin embargo, si nos paramos a pensar… una llamada a una API no es más que un tipo de acción asíncrona que pasa por diferentes estados, algo que es muy sencillo modelar a través de Redux de modo que podamos controlar el estado de las llamadas que hacemos así como cancelarlas si fuera necesario.

Requisitos previos

Como comentaba en el punto anterior, para llevar a cabo lo que os propongo en este artículo necesitaremos tener instalado redux-thunk, de modo que podamos realizar llamadas asíncronas dentro de nuestras acciones de redux para posteriormente realizar el dispatch a nuestros reducers.

La configuración de este middleware es bastante sencilla como podéis ver en su documentación y, una vez realizada, podremos realizar llamadas de este tipo:

Normalmente, cuando realizamos dispatch de una acción, el objeto devuelto es enviado al reducer asociado, pero gracias a redux-thunk, podremos devolver funciones que serán invocadas en vez de ser enviadas a los reducers. Estas funciones pueden hacer lo que queramos y, a diferencia de las action creators, los thunks no devuelven un objeto-acción sino que devuelven la función que deberá ser invocada.

Los argumentos de esa función son la propia función dispatch y la función getState, que nos permitirá consultar el estado de redux en el caso de que así lo necesitemos. La ventaja de esto es que podremos realizar el dispatch de cuantas acciones queramos en vez de una sola como sucede con los action creators estándars de redux.

Los estados de una llamada

Dicho esto, vamos a analizar todos los estados por los que pasa cualquier llamada a una API externa. Estaréis de acuerdo conmigo en que podrían resumirse en los siguientes:

Es decir, una llamada a una API puede estar ejecutándose (loading), haber sido cancelada (cancelled), haber fallado (error) y tener asociado un controlador de cara a poder ser cancelada. Es discutible si en este estado tendría sentido almacenar la respuesta obtenida, pero por la forma en que diseñé el resto la aplicación no lo necesitábamos. No obstante, es probable que en otra arquitectura tenga sentido y por mi parte lo veo una muy buena idea añadir al estado esos dos elementos.

Para cada uno de estos estados lo adecuado es tener un action creator que nos permita modificarlo, algo de este tipo:

Y es ahora cuando la cosa se complica un pelín, ya que como veis cada action creator recibe un id, el cual representa el identificador asociado a la llamada cuyo estado queremos modificar.

Un ID para cada request

Evidentemente no solo tendremos un tipo de request en nuestra aplicación, sino que tendremos múltiples llamadas para acceder o modificar los recursos de nuestra aplicación, por tanto, nuestro estado en redux para esta parte debería tener un aspecto similar al siguiente:

De modo que podamos acceder o modificar el estado de una llamada en concreto gracias a su id. A continuación os contaré mi planteamiento para esto, para el cual se me ocurren unas cuantas mejoras pero que creo que es un buen punto de partido. Comencemos.

Como veis, el reducer es bastante sencillo, y en función de cada acción establecemos el estado de la llamada asociada al id enviado según la acción correspondiente. Además, almaceno también el token que me permitirá cancelar la llamada de necesitarlo (por ejemplo, en el método componentWillUnmount que veremos más adelante).

Vuelvo a dejaros el código que añadí un par de secciones atrás para que no tengáis que andar subiendo, pero si os fijáis en él tan solo creo una acción para modificar el estado de una llamada en concreto en función de lo que necesitemos:

Aquí ya empieza a tornarse interesante la cosa. Supongamos que queremos cancelar una llamada que estaba en curso (por ejemplo, el usuario se fue de la vista antes de que se completase y no queremos realizar un setState en un componente que va a desmontarse). Creo que ésta es una de las principales utilidades que tiene lo que cuento en este artículo y es un pequeño atisbo de la versatilidad que nos ofrece la combinación de redux y redux-thunk a la hora de manejar el estado de nuestra aplicación.

Como os decía, si queremos cancelar una llamada, nuestro thunk debería tener un aspecto similar al siguiente código:

Además, es un código bastante didáctico que nos permite ver la forma en que podemos acceder al estado de redux por medio de la función getState así como realizar el dispatch de un action creator una vez que hemos realizado las operaciones que necesitemos. Por lo demás, lo que hago es comprobar si la llamada todavía se está ejecutando y en ese caso emplear el token que me proporcionó la librería axios para cancelarla. No os preocupéis, en el siguiente punto veremos de dónde viene ese token!

Seguimos con la parte interesante ya que todavía nos falta ver la forma de realizar una llamada a la API. Para ello, emplearemos otro thunk, en el cual llevaremos a cabo todo el proceso de llamar a la API, actualizando el estado de la llamada conforme pasa por sus diferentes fases. Os dejo primero el código para a continuación explicároslo detenidamente.

Son dos gists. En el primero podréis ver mi clase request que se sitúa por encima de axios para que cuando llegue el momento de migrar a API Fetch no tarde un par de horas modificando todas las llamadas y en el segundo está el thunk propiamente dicho.

Request.js
Create Request Thunk

Como la clase Request es bastante autoexplicativa me centraré en el segundo gist que es donde está chicha.

  1. Lo primero de todo es obtener un token de cancelación procedente de axios por medio de la llamada request.getCancelTokenSource .
  2. A continuación actualizo el estado de la llamada a STARTED por medio del action creator: requestStartedAction(requestId, cancelTokenSource)
  3. Realizo la llamada enviando los parámetros necesario al método send de la clase Request así como el token que axios asociará a dicha llamada para poderla cancelar.
  4. Si se completa correctamente, lanzo la acción requestSucceededAction y devuelvo el resultado de la llamada ( Recordad que una vez que el thunk para por el middleware obtenemos lo que hayamos devuelto dentro del mismo). Aquí cabría también almacenar en redux, como decía al principio del artículo, el resultado de la llamada, pero en mi caso no lo he necesitado.
  5. En el caso de que falle la llamada puede haber sido bien porque se haya cancelado desde el componente que la inició o bien por algún fallo en el backend. En este último caso lanzo la acción requestFailedAction

Gracias a esto, podemos controlar todo el estado de la llamada y mantener en el estado su token de cancelación en el caso de que lo necesitemos.

Muy bien… ¿pero cómo uso esto?

Una vez montado esto, la idea es que todas nuestras llamadas pasen por aquí para que así podamos controlar el estado de las mismas y, por ejemplo, centralizar la renovación de nuestro token de autenticación (JWT) en el caso de que el backend nos pida renovarlo; algo similar a esto:

Dicho esto, cuando queramos realizar una llamada a la API, por ejemplo para obtener los artículos de un blog, podremos realizar lo siguiente:

Es decir, en vez de hacer directamente la llamada a la API, realizaremos el dispatch del thunk createRequest pasándole como argumento los parámetros de la llamada, para lo cual tendremos funciones encargadas de generar esos parámetros para cada llamarlos y asociarlos a un id:

Este id será el que posteriormente nos permita identificar la llamada en redux y gestionar su estado y, llegado el caso, cancelarla.

Cancelando una llamada

En el caso de que necesitemos cancelar una llamada, será tan sencillo como realizar lo siguiente (voy a hacerlo dentro del método componentWillUnmount ya que probablemente es el sitio más habitual donde podéis necesitarlo):

componentWillUnmount() {  const { cancelRequestThunk } = this.props;  cancelRequestThunk(requestIds.GET_POSTS);}

De este modo, la llamada quedará cancelada y evitaréis realizar algún setState a destiempo que suponga un memory leak.

Conclusiones y mejoras

Si habéis llegado hasta aquí seguro que se os ocurren mil formas de mejorar este sistema y de problemas que pueden surgir empleándolo. Lo desarrollé durante el verano pasado y ya tengo apuntadas algunas cosas que mejorar, por ejemplo la posibilidad de que haya dos llamadas del mismo tipo (dos componentes lanzando la llamada getPosts en paralelo, por ejemplo), algo que de momento en la aplicación donde estoy probando no sucede pero que sí me gustaría controlar.

Además, creo que es posible abstraer un poco más la forma en la que el componente puede cancelar una llamada iniciada por él.

Sin embargo, creo que es un buen punto de partida y que además soluciona otro tipo de problemas como puede ser el de renovar el JWT cuando este expira en un mismo sitio a la vez que mantenemos un estado coherente dentro de la propia aplicación.

Espero vuestros comentarios con críticas, sugerencias y cualquier aportación que queráis añadir.

¿Quieres recibir más artículos como este?

Suscríbete a nuestra newsletter:

Written by

Entre paseo y paseo con Simba desarrollo en Symfony y React

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store