Este error se produce cuando intentamos actualizar el estado de un componente que ya no está montado. Esto puede ocurrir cuando el componente se desmonta antes de que se complete una petición asíncrona, por ejemplo:
function Movies () {
const [movies, setMovies] = useState([])
useEffect(() => {
fetchMovies().then(() => {
setMovies(data.results)
})
})
if (!movies.length) return null
return (
<section>
{movies.map(movie => (
<article key={movie.id}>
<h2>{movie.title}</h2>
<p>{movie.overview}</p>
</article>
))}
</section>
)
}
Parece un código inofensivo, pero imagina que usamos este componente en una página. Si el usuario navega a otra página antes de que se complete la petición, el componente se desmontará y React lanzará el error, ya que intentará ejecutar el setMovies
en un componente (Movies) que ya no está montado.
Para evitar este error, podemos usar una variable booleana con useRef
que nos indique si el componente está montado o no. De esta manera, podemos evitar que se ejecute el setMovies
si el componente no está montado:
function Movies () {
const [movies, setMovies] = useState([])
const mounted = useRef(false)
useEffect(() => {
mounted.current = true
fetchMovies().then(() => {
if (mounted.current) {
setMovies(data.results)
}
})
return () => mounted.current = false
})
// ...
}
Esto soluciona el problema pero no evita que se haga la petición aunque el componente ya no esté montado. Para cancelar la petición y así ahorrar transferencia de datos, podemos abortar la petición usando la API AbortController
:
function Movies () {
const [movies, setMovies] = useState([])
useEffect(() => {
// creamos un controlador para abortar la petición
const abortController = new AbortController()
// pasamos el signal al fetch para que sepa que debe abortar
fetchMovies({ signal: abortController.signal })
.then(() => {
setMovies(data.results)
}).catch(error => {
if (error.name === 'AbortError') {
console.log('fetch aborted')
}
})
return () => {
// al desmontar el componente, abortamos la petición
// sólo funcionará si la petición sigue en curso
abortController.abort()
}
})
// ...
}
// Debemos pasarle el parámetro signal al `fetch`
// para que enlace la petición con el controlador
const fetchMovies = ({ signal }) => {
return fetch('https://api.themoviedb.org/3/movie/popular', {
signal // <--- pasamos el signal
}).then(response => response.json())
}
Sólo ten en cuenta la compatibilidad de AbortController
en los navegadores. En caniuse puedes ver que no está soportado en Internet Explorer y versiones anteriores de Chrome 66, Safari 12.1 y Edge 16.