Si estás trabajando con React en algún momento puede haberte llegado a la consola del navegador un aviso sobre una pérdida de memoria al haber actualizado el estado de un componente estando desmontado:
Muchos de nosotros evitamos este problema recurriendo a la típica comprobación de isMounted
, donde básicamente controlábamos el desmonte de un componente de React mediante una referencia que consultábamos justo antes de hacer la actualización del estado.
Recordemos que en las primeras versiones de React teníamos esta propiedad de forma interna en la librería, pero fue deprecada allá por el 2015 porque se trataba de un anti-patrón... una mala práctica que el equipo de React avisaba y desaconsejaba su uso. Eso sí, al menos nos mostraban el camino correcto 😅
Ahora bien, ¿qué podemos hacer para solucionar este problema?
Podemos continuar con el uso del isMounted
, pero como bien nos recomienda el artículo, lo suyo sería cancelar la promesa para que no se llegue a producir esa actualización del estado.
Pero si bien la forma anterior está correcta, no es lo más óptimo. Si nos fijamos en lo que sucede en nuestro navegador, vemos cómo efectivamente no tenemos esa pérdida de memoria porque no se intenta actualizar el estado cuando el componente está desmontado, pero lo que sí podemos ver es que nuestra llamada a la API continua su curso, descargando la información de la llamada aunque no haga nada con su respuesta.
Hemos solucionado un problema pero seguimos arrastrando una pérdida de recursos.
AbortController con Fetch
Hoy te propongo hacer uso de la interface AbortController que te permitirá abortar o cancelar una solicitud web cuando quieras.
Su uso es muy sencillo como verás a continuación:
Creamos el controlador
const controller = new AbortController();
Este objeto nos proporciona un método abort
que será el que nos permitirá abortar nuestra solicitud web cuando deseemos. También nos retorna una propiedad signal
que será la que le pasaremos a nuestra configuración fetch
para que enlace dicha funcionalidad.
Cuando abort()
sea llamado:
controller.signal
emitirá un eventoabort
.controller.signal.aborted
serátrue
.
Cómo lo usamos
const controller = new AbortController();
fetch(url, {
signal: controller.signal
});
Para nuestra suerte, fetch
permite el uso de la interface AbortController
así que con el código anterior estará preparado para actuar en caso de que nuestra señal sea abortada.
Cancelando una llamada
Ahora para cancelar nuestra llamada a la API, sólo debemos ejecutar el método abort
de nuestro objeto controller
:
controller.abort();
Ahora veremos cómo podemos crearnos un hook para encapsular esta funcionalidad 👇
useAbortableSignal Hook
export const useAbortableSignal = () => {
const [controller] = useState(() => new AbortController());
useEffect(() => {
return () => {
controller.abort();
};
}, []);
return {
signal: controller.signal
};
};
Su uso en un componente
export const useData = () => {
const { signal } = useAbortableSignal();
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState('');
const [data, setData] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
try {
const result = await getData(signal);
setData(result);
} catch (err) {
if (err) {
const isAborted = err.name === 'AbortError';
if (isAborted) {
return;
}
setError('Something went wrong while fetching data');
}
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
return { isLoading, data, error };
};
Con el código anterior además estás controlando que el error manejado en el
catch
sea porque la solicitud ha sido cancelada y lo podríamos omitir sin problema.
Y ahora si ves el resultado en la pestaña de red de las herramientas de desarrollador... 😊
¿Puedo usar el mismo controlador para varias promesas?
Sí, y de hecho si lo que quieres es cancelar la ejecución de las llamadas a la API en un componente, tiene sentido que todas las solicitudes compartan el mismo controlador porque cuando se desmonte el componente, abortaremos el controlador cancelando todas las solicitudes vivas en el componente.
Compatibilidad
En el momento de haber escrito este artículo, la tabla de compatibilidad era la de la imagen, pero te aconsejo que la revises nuevamente en este enlace si tienes alguna duda.
Código de ejemplo y comparativa
Ver completo: codesandbox.io/s/abortable-fetch-ffkyzh