HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactCritical

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

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
updatecomponentunmountedstatereactcanperform

Problem

Problem

I am writing an application in React and was unable to avoid a super common pitfall, which is calling setState(...) after componentWillUnmount(...).

I looked very carefully at my code and tried to put some guarding clauses in place, but the problem persisted and I am still observing the warning.

Therefore, I've got two questions:

  • How do I figure out from the stack trace, which particular component and event handler or lifecycle hook is responsible for the rule violation?



  • Well, how to fix the problem itself, because my code was written with this pitfall in mind and is already trying to prevent it, but some underlying component's still generating the warning.



Browser console

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 TextLayerInternal (created by Context.Consumer)
    in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29


Code

Book.tsx

```
import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';

const DEFAULT_WIDTH = 140;

class Book extends React.Component {
setDivSizeThrottleable: () => void;
pdfWrapper: HTMLDivElement | null = null;

Solution

Here is a React Hooks specific solution for
Error

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

Solution

You can declare let isMounted = true inside useEffect, which will be changed in the cleanup callback, as soon as the component is unmounted. Before state updates, you now check this variable conditionally:

useEffect(() => {
  let isMounted = true;               // note mutable flag
  someAsyncOperation().then(data => {
    if (isMounted) setState(data);    // add conditional check
  })
  return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);                               // adjust dependencies to your needs




const Parent = () => {
const [mounted, setMounted] = useState(true);
return (

Parent:
setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child

{mounted && }


Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.


);
};

const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useEffect(() => {
let isMounted = true;
fetchData();
return () => {
isMounted = false;
};

// simulate some Web API fetching
function fetchData() {
setTimeout(() => {
// drop "if (isMounted)" to trigger error again
// (take IDE, doesn't work with stack snippet)
if (isMounted) setState("data fetched")
else console.log("aborted setState on unmounted component")
}, 4000);
}
}, []);

return Child: {state};
};

ReactDOM.render(, document.getElementById("root"));



var { useReducer, useEffect, useState, useRef } = React




Extension: Custom useAsync Hook

We can encapsulate all the boilerplate into a custom Hook, that automatically aborts async functions in case the component unmounts or dependency values have changed before:

function useAsync(asyncFn, onSuccess) {
  useEffect(() => {
    let isActive = true;
    asyncFn().then(data => {
      if (isActive) onSuccess(data);
    });
    return () => { isActive = false };
  }, [asyncFn, onSuccess]);
}




// custom Hook for automatic abortion on unmount or dependency change
// You might add onFailure for promise errors as well.
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data)
else console.log("aborted setState on unmounted component")
});
return () => {
isActive = false;
};
}, [asyncFn, onSuccess]);
}

const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useAsync(simulateFetchData, setState);
return Child: {state};
};

const Parent = () => {
const [mounted, setMounted] = useState(true);
return (

Parent:
setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child

{mounted && }


Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.


);
};

const simulateFetchData = () => new Promise(
resolve => setTimeout(() => resolve("data fetched"), 4000));

ReactDOM.render(, document.getElementById("root"));



var { useReducer, useEffect, useState, useRef } = React




More on effect cleanups: Overreacted: A Complete Guide to useEffect

Code Snippets

useEffect(() => {
  let isMounted = true;               // note mutable flag
  someAsyncOperation().then(data => {
    if (isMounted) setState(data);    // add conditional check
  })
  return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);                               // adjust dependencies to your needs
function useAsync(asyncFn, onSuccess) {
  useEffect(() => {
    let isActive = true;
    asyncFn().then(data => {
      if (isActive) onSuccess(data);
    });
    return () => { isActive = false };
  }, [asyncFn, onSuccess]);
}

Context

Stack Overflow Q#53949393, score: 473

Revisions (0)

No revisions yet.