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

  1. Track mounted state yourself: set this._isMounted = true in componentDidMount, false in componentWillUnmount
  2. Better: cancel async work in componentWillUnmount (AbortController/subscription teardown) instead of checking mounted before setState
  3. Replace replaceState(next) with setState handing every top-level key you want replaced
  4. 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

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.