material-components-web-react icon indicating copy to clipboard operation
material-components-web-react copied to clipboard

Can't perform a React state update on an unmounted component

Open cdock1029 opened this issue 5 years ago • 17 comments

Seeing this error when I hide a div that contains many ChipSet components.

index.js:1452 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in RippledComponent
    in div
    in ChipSet

It seems to happen right before execution of this line:

https://github.com/material-components/material-components-web-react/blob/a468f024df300c535d220f9bbc493e5c67af77d5/packages/chips/Chip.js#L74

I see there is no "mounted" check prior to this setState call, maybe this could be the source of error.

cdock1029 avatar Nov 13 '18 03:11 cdock1029

@cdock1029 ah yes, we should have an isMounted check before both the remove and addClass methods (similar to https://github.com/material-components/material-components-web-react/blob/master/packages/ripple/index.js#L34). The foundation is calling these methods, but its most likely after they have been destroyed.

If you'd like, please feel free to open a PR following the pattern in the above link. Otherwise we will get to it asap.

moog16 avatar Nov 13 '18 19:11 moog16

Fix it with this technique.

Look where I added _isMounted and follow the same in you code

class Page extends Component {
  _isMounted = false;

  state = {
    isLoading: true
  }

  componentDidMount() {
    this._isMounted = true;
  
    callAPI_or_DB(...).then(result => {
      if (this._isMounted) {
        this.setState({isLoading: false})
      }
    });
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  render() {
    return (
      <div>Whatever</div>
    );
  }
}

export default Page;

mohokh67 avatar Dec 22 '18 10:12 mohokh67

Thanks @mohokh67! For a more permanent solution, the Chip component should have built in internally.

moog16 avatar Dec 26 '18 18:12 moog16

Try https://github.com/iShawnWang/withUnmounted package as an elegant solution

iShawnWang avatar Feb 10 '19 06:02 iShawnWang

尝试使用https://github.com/iShawnWang/withUnmounted包作为优雅的解决方案

npm ERR! 404 Not Found: @iShawnWang/withUnmounted@latest

Vivapercuore avatar Mar 11 '19 08:03 Vivapercuore

Sorry for the case-sensitive : npm install --save @ishawnwang/withunmounted works fine ~

iShawnWang avatar Mar 12 '19 10:03 iShawnWang

I get similar warning in tests

     <JssProvider generateClassName={generateClassName}>
        <SnackbarProvider>
          <StaticRouter location="/" context={{}}>
            <Provider store={mStore}>
              <Route path="/" component={Dashboard} exact />
            </Provider>
          </StaticRouter>
        </SnackbarProvider>
      </JssProvider>,

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in SnackbarProvider (at Dashboard.test.jsx:41) in JssProvider (created by WrapperComponent) in WrapperComponent

saeedsq avatar Oct 22 '19 07:10 saeedsq

@mohokh67 What about solution for a functional component?

5ervant avatar Oct 27 '19 17:10 5ervant

@5ervant you can use useRef for check if the component still mounted

example

piperubio avatar Nov 18 '19 01:11 piperubio

look at it: https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component

mohammad-khoddami avatar Dec 03 '19 08:12 mohammad-khoddami

For functional components you can use a custom hook:

import { useEffect, useRef } from "react";

const useIsMounted = () => {
  const isMounted = useRef(false);
  useEffect(() => {
    isMounted.current = true;
    return () => (isMounted.current = false);
  }, []);
  return isMounted;
};

and then use it in your component:

const MyComp = () => {
  const isMounted = useIsMounted();

  useEffect(() => {
    if (isMounted.current) {
      // update your state here
    }
  }, [isMounted]);

  /// ...
};

PS: This might not be worth while.

SalahHamza avatar Dec 04 '19 15:12 SalahHamza

I have met this problem in a situation where the keys were massed up. So it might be coming from above the component itself. In my case the mistake was caused by how I have been generating the keys:

<ul>
{collection.map(o => <li key={JSON.stringify(o)}> {o.title} </li>)}
</ul>

That causes a memory leak as React can't notice some elements must be removed or created as the key changes on any other change in the collection element. My conclusion is that key should be unique during the life cycle so the state mechanism can rely on it.

andreiQuant avatar Jan 09 '20 11:01 andreiQuant

how to resolve can't perform react state while using firestore

muhammadtalhasiddiqui avatar Mar 06 '20 15:03 muhammadtalhasiddiqui

Try https://github.com/iShawnWang/withUnmounted package as an elegant solution

thanks @iShawnWang , work fine for me

arimursandhi avatar Apr 02 '20 00:04 arimursandhi

i had to rerender the whole app in a specific case, so had changed key in useEffect of the container component. And this issue was coming.

aanand07 avatar Sep 22 '20 12:09 aanand07

what can I do I get the same problem that is can't perform a react state on an unmounted component...

`

const Header = () => { const [collection, setCollection] = useState([]);

const FetchCollection = async () => { const response = await fetch('http://localhost:5000/collection'); setCollection(await response.json([])); } console.log(collection)

useEffect(() => { FetchCollection(); }, []); `

RajatShinde3 avatar Apr 07 '22 04:04 RajatShinde3

what can I do I get the same problem that is can't perform a react state on an unmounted component...

`

const Header = () => { const [collection, setCollection] = useState([]);

const FetchCollection = async () => { const response = await fetch('http://localhost:5000/collection'); setCollection(await response.json([])); } console.log(collection)

useEffect(() => { FetchCollection(); }, []); `

In your case if the component is unmounted before the fetch return the setCollection will still be called. Also you don't return anything out of Header and I am not sure if that is intended.

One could use a ref to understand if the component was unmounted:

const Header = () => {
 const ref = useRef(null);
const [collection, setCollection] = useState([]);
const fetchCollection = async () => {
const response = await fetch('http://localhost:5000/collection');
ref.current && setCollection(await response.json([]));
}

useEffect(() => {
ref.current = true
fetchCollection()
return () => {
   ref.current = null
}
})
return <header>
{collection.map(item => <div key={JSON.stringify(item)}></div>)}
</hrader>

}

andreiQuant avatar Apr 19 '22 10:04 andreiQuant