facebook/react · warning
%s(...) is deprecated in plain JavaScript React classes. %s
Error message
%s(...) is deprecated in plain JavaScript React classes. %s
What it means
In dev builds, React defines isMounted and replaceState as getter-only properties on Component.prototype (ReactBaseClasses.js:110) that console.warn and return undefined when accessed. These createClass-era APIs were never part of the ES6 base class; the getters exist purely to catch ported code. Because the getter returns undefined, code like this.isMounted() warns and then throws TypeError: not a function.
Source
Thrown at packages/react/src/ReactBaseClasses.js:110
* modern base class. Instead, we define a getter that warns if it's accessed.
*/
if (__DEV__) {
const deprecatedAPIs = {
isMounted: [
'isMounted',
'Instead, make sure to clean up subscriptions and pending requests in ' +
'componentWillUnmount to prevent memory leaks.',
],
replaceState: [
'replaceState',
'Refactor your code to use setState instead (see ' +
'https://github.com/facebook/react/issues/3236).',
],
};
const defineDeprecationWarning = function (methodName, info) {
Object.defineProperty(Component.prototype, methodName, {
get: function () {
console.warn(
'%s(...) is deprecated in plain JavaScript React classes. %s',
info[0],
info[1],
);
return undefined;
},
});
};
for (const fnName in deprecatedAPIs) {
if (deprecatedAPIs.hasOwnProperty(fnName)) {
defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
}
}
}
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
View on GitHub (pinned to eafeac097b)
Solutions
- Track mounted state yourself: set this._isMounted = true in componentDidMount, false in componentWillUnmount
- Better: cancel async work in componentWillUnmount (AbortController/subscription teardown) instead of checking mounted before setState
- Replace replaceState(next) with setState handing every top-level key you want replaced
- Add eslint-plugin-react's no-is-mounted rule to block regressions
Example fix
// before
fetchUser(id).then(u => { if (this.isMounted()) this.setState({user: u}); });
// after
componentDidMount() {
this._cancelled = false;
fetchUser(id).then(u => { if (!this._cancelled) this.setState({user: u}); });
}
componentWillUnmount() { this._cancelled = true; } Defensive patterns
Strategy: validation
Validate before calling
// detect the dev-only getter WITHOUT accessing it (access triggers the warning)
function hasDeprecatedClassAPIs() {
if (process.env.NODE_ENV !== 'production') {
const d = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(Object.getPrototypeOf(this)) ?? {},
'isMounted',
);
return Boolean(d && d.get);
}
return false;
} Type guard
function definesDeprecatedClassAPI(instance, name) {
let proto = Object.getPrototypeOf(instance);
while (proto && proto !== Object.prototype) {
const d = Object.getOwnPropertyDescriptor(proto, name);
if (d && d.get) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
} Prevention
- Enable eslint-plugin-react's no-is-mounted rule
- Grep the codebase for isMounted and replaceState before React 18/19 upgrades
- Use componentWillUnmount cleanup (flags, AbortController) instead of mounted checks
- Remember even `if (this.isMounted)` triggers the warning - it fires on property access, not on call
When it happens
Trigger: Any access - even a truthiness check or feature test - of this.isMounted or this.replaceState inside a class component, including access from mixins or utilities holding the instance. Production builds define nothing, so this.isMounted is plain undefined.
Common situations: Legacy code guarding async setState after unmount; components migrated from React.createClass; old tutorials and enterprise code copied between projects for years.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/a340dc78ee58eaee.
Report an issue: GitHub.