How to Fix 'TypeError: Super expression must either be null or a function' in React Class Components
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: A React class component is calling
super()against a base class that resolves toundefinedat runtime — most commonly caused by a circular import, a missing/incorrect import path, or a typo in the extended class name. - How to fix it: Audit the import chain for the base class (
React.Component,React.PureComponent, or a custom base). Eliminate circular dependencies. Verify the import resolves to an actual function before the class is evaluated. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your component and it will identify the broken import chain and emit corrected code without sending your source to any external server.
The Incident (What does the error mean?)
Raw error output:
TypeError: Super expression must either be null or a function
at _inherits (react-dom.development.js)
at new MyComponent
at renderWithHooks
at mountIndeterminateComponent
JavaScript's class syntax enforces at the VM level that whatever appears after extends must be either null or a callable function. When Babel/webpack transpiles your class component and the base class resolves to undefined at the moment the class expression is evaluated, the JS engine throws this error before your constructor body even runs. The entire React render cycle for that subtree halts. If this component sits near the root of your tree and you have no Error Boundary, your entire application goes blank.
The Attack Vector / Blast Radius
This is a runtime crash, not a compile-time error. Your CI pipeline likely passed. Your linter likely passed. The failure surfaces only when the module graph is fully resolved in the browser or Node SSR environment.
Primary causes ranked by frequency:
- Circular imports —
ComponentAimportsComponentBwhich importsComponentA. At the pointComponentB's class is evaluated,ComponentAis stillundefinedin the module registry. This is the #1 cause in large codebases. - Incorrect import path —
import { Component } from 'react'accidentally becomesimport { Component } from './react'(a local file that exports nothing useful). - Custom base class not exported correctly — A shared
BaseComponentclass is exported asexport defaultbut imported as a named import{ BaseComponent }, resolving toundefined. - Tree-shaking or bundler misconfiguration — Dead-code elimination incorrectly removes the base class export in production builds only, making this a prod-only outage.
Blast radius: Any component in the subtree below the broken class fails to mount. With SSR (Next.js, Remix), this can produce a 500 on every page request until deployed fix lands.
How to Fix It (The Solution)
Basic Fix — Verify and correct the React import
- import React, { Component } from './react'; // wrong: local file
+ import React, { Component } from 'react'; // correct: node_modules
- class MyComponent extends Compnent { // wrong: typo
+ class MyComponent extends Component { // correct
render() {
return <div>Hello</div>;
}
}
Fix — Circular dependency (most insidious case)
// ComponentB.js
- import { ComponentA } from './ComponentA'; // creates cycle if A imports B
+ // Break the cycle: extract shared logic to a third file (utils.js or BaseComponent.js)
+ import { SharedBase } from './BaseComponent';
- class ComponentB extends ComponentA {
+ class ComponentB extends SharedBase {
render() { return <div />; }
}
Fix — Custom base class export/import mismatch
// BaseComponent.js
- export default class BaseComponent extends React.Component {} // default export
// ConsumerComponent.js
- import { BaseComponent } from './BaseComponent'; // named import of a default = undefined
+ import BaseComponent from './BaseComponent'; // correct: default import
class ConsumerComponent extends BaseComponent {}
Enterprise Best Practice
Do not use class inheritance chains beyond React.Component. The React team has explicitly discouraged deep class hierarchies since 2018. Refactor shared logic into custom hooks or Higher-Order Components. If you must maintain a class component (legacy code, third-party lib constraint), enforce a strict module boundary:
// shared/BaseComponent.ts — single source of truth
- export class BaseComponent extends React.Component<any, any> {} // any = liability
+ export class BaseComponent<P = Record<string, unknown>, S = Record<string, unknown>> extends React.Component<P, S> {}
// Enforce no circular deps at the module level with eslint-plugin-import:
// .eslintrc
+ "rules": {
+ "import/no-cycle": ["error", { "maxDepth": 3 }]
+ }
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. eslint-plugin-import — no-cycle rule (non-negotiable)
{
"rules": {
"import/no-cycle": ["error", { "maxDepth": 5, "ignoreExternal": true }]
}
}
This catches circular imports at lint time, before the code ever reaches a browser.
2. madge in your pre-commit hook or CI step
# package.json scripts
"check:cycles": "madge --circular --extensions ts,tsx src/ && echo 'No circular deps'"
Fail the pipeline if any cycle is detected.
3. TypeScript strict mode
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true
}
}
A base class resolving to undefined will surface as a TS2507 (Type 'undefined' is not a constructor function type) at compile time — before runtime, before deployment.
4. Bundle analysis in staging
Run webpack-bundle-analyzer or vite-bundle-visualizer on every staging deploy. Prod-only tree-shaking bugs show up as missing modules in the chunk graph before they hit users.
5. React Error Boundaries as a last line of defense
class AppErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean}> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error) { reportToSentry(error); }
render() {
return this.state.hasError ? <FallbackUI /> : this.props.children;
}
}
This won't prevent the error, but it contains the blast radius to a subtree instead of a full white screen.