I think the title says it all. The yellow warning is displayed every time I unmount a component that is still fetching.
ConsoleWarning: Can't call
setState
(orforceUpdate
) on an unmounted component. This is a no-op, but ... To fix, cancel all subscriptions and asynchronous tasks in thecomponentWillUnmount
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); }); }
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With