React isMounted
ID |
javascript.react_is_mounted |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
deprecated-api, react, reliability |
Description
Reports a call to this.isMounted().
The receiver has to be this: isMounted is an ordinary method name, and an object that defines one of its own is not reported.
Rationale
import React from "react";
class Rows extends React.Component {
// Bad
load() {
fetch('/rows').then(rows => {
if (this.isMounted()) {
this.setState({ rows });
}
});
}
}
-
isMountedwas React’s guard against updating a component that had gone away, and it was removed. A component written as an ES class never had it, so the call is aTypeErrorthe first time that branch runs — inside a promise continuation, where an unhandled rejection is easy to lose. -
It was removed because it does not fix what it appears to fix. The request still completes, the timer still fires, the subscription still holds a reference to the unmounted component. What leaks is the work, not the update.
-
The guard hides the leak by removing its one visible symptom, the warning about updating an unmounted component.
Remediation
Cancel the work when the component goes away, so there is no update left to guard.
import React from "react";
class Rows extends React.Component {
// Good — an abortable request
componentDidMount() {
this.controller = new AbortController();
fetch('/rows', { signal: this.controller.signal })
.then(rows => this.setState({ rows }));
}
componentWillUnmount() {
this.controller.abort();
}
}
import { useEffect, useState } from "react";
// Good — the function-component form, cleaned up by the effect
function Rows() {
const [rows, setRows] = useState([]);
useEffect(() => {
const controller = new AbortController();
fetch('/rows', { signal: controller.signal }).then(setRows);
return () => controller.abort();
}, []);
return rows.length;
}