patternjavascriptreactCritical
Can't perform a React state update on an unmounted component
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
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:
Browser console
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;
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:29Code
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
Extension: Custom
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:
More on effect cleanups: Overreacted: A Complete Guide to useEffect
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 needsconst 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 HookWe 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 needsfunction 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.