Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel a fetch on componentWillUnmount

Tags:

reactjs

I think the title says it all. The yellow warning is displayed every time I unmount a component that is still fetching.

Console

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but ... To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

  constructor(props){     super(props);     this.state = {       isLoading: true,       dataSource: [{         name: 'loading...',         id: 'loading',       }]     }   }    componentDidMount(){     return fetch('LINK HERE')       .then((response) => response.json())       .then((responseJson) => {         this.setState({           isLoading: false,           dataSource: responseJson,         }, function(){         });       })       .catch((error) =>{         console.error(error);       });   } 
like image 415
João Belo Avatar asked Apr 18 '18 18:04

João Belo


People also ask

How do you cancel async task in React?

Also, in order to cancel an active fetch request, you need to use an AbortController instance. Try the demo. let controller = new AbortController() creates an instance of the abort controller. Then await fetch(..., { signal: controller.

How do you stop components from unmounting?

Using react-router you can easily prevent route change(which will prevent component unmount) by using Prompt . You need to manually pass the getUserConfirmation prop which is a function. You can modify this function as you like in any Router(Browser, Memory or Hash) to create your custom confirmation dialog(eg.

Why is componentWillMount unsafe?

These methods are considered “unsafe” because the React team expect code that depends on these methods to be more likely to have bugs in future versions of React. Depending on the objective of the code, you can remove the use of componentWillMount entirely with other lifecycle methods.


1 Answers

When you fire a Promise it might take a few seconds before it resolves and by that time user might have navigated to another place in your app. So when Promise resolves setState is executed on unmounted component and you get an error - just like in your case. This may also cause memory leaks.

That's why it is best to move some of your asynchronous logic out of components.

Otherwise, you will need to somehow cancel your Promise. Alternatively - as a last resort technique (it's an antipattern) - you can keep a variable to check whether the component is still mounted:

componentDidMount(){   this.mounted = true;    this.props.fetchData().then((response) => {     if(this.mounted) {       this.setState({ data: response })     }   }) }  componentWillUnmount(){   this.mounted = false; } 

I will stress that again - this is an antipattern but may be sufficient in your case (just like they did with Formik implementation).

A similar discussion on GitHub

EDIT:

This is probably how would I solve the same problem (having nothing but React) with Hooks:

OPTION A:

import React, { useState, useEffect } from "react";  export default function Page() {   const value = usePromise("https://something.com/api/");   return (     <p>{value ? value : "fetching data..."}</p>   ); }  function usePromise(url) {   const [value, setState] = useState(null);    useEffect(() => {     let isMounted = true; // track whether component is mounted      request.get(url)       .then(result => {         if (isMounted) {           setState(result);         }       });      return () => {       // clean up       isMounted = false;     };   }, []); // only on "didMount"    return value; } 

OPTION B: Alternatively with useRef which behaves like a static property of a class which means it doesn't make component rerender when it's value changes:

function usePromise2(url) {   const isMounted = React.useRef(true)   const [value, setState] = useState(null);     useEffect(() => {     return () => {       isMounted.current = false;     };   }, []);    useEffect(() => {     request.get(url)       .then(result => {         if (isMounted.current) {           setState(result);         }       });   }, []);    return value; }  // or extract it to custom hook: function useIsMounted() {   const isMounted = React.useRef(true)    useEffect(() => {     return () => {       isMounted.current = false;     };   }, []);    return isMounted; // returning "isMounted.current" wouldn't work because we would return unmutable primitive } 

Example: https://codesandbox.io/s/86n1wq2z8

like image 183
Tomasz Mularczyk Avatar answered Oct 09 '22 02:10

Tomasz Mularczyk