This TypeScript/JavaScript error occurs when you try to access properties or methods using "this" in a derived class constructor before calling the parent class constructor with super(). The parent class must be initialized first before the derived class can safely use its own properties.
The error "'super' must be called before accessing properties on 'this' in a derived class" is a fundamental JavaScript/TypeScript rule enforcement. In class-based inheritance, when you extend a parent class, the derived class's constructor must call super() before it can access the "this" keyword. This is because: 1. **Parent initialization first**: The parent class constructor needs to set up the instance first 2. **"this" is undefined**: Before super() is called, "this" doesn't refer to a valid object 3. **Inheritance chain**: Properties and methods from the parent class aren't available until after super() The error prevents runtime errors where you might try to access properties that haven't been initialized yet. This is especially important in TypeScript with strict class initialization checking enabled.
The most common fix is to ensure super() is called before any "this" references:
// ❌ WRONG: Accessing "this" before super()
class Derived extends Base {
constructor(value: string) {
this.someProperty = value; // Error: "this" before super()
super();
}
}
// ✅ CORRECT: Call super() first
class Derived extends Base {
constructor(value: string) {
super(); // Parent initialized first
this.someProperty = value; // Now safe to use "this"
}
}Always call super() as the first statement in your derived class constructor.
If the parent class constructor requires arguments, pass them to super():
// ❌ WRONG: Trying to process data before passing to parent
class User extends Person {
constructor(name: string, age: number) {
const processedName = name.trim(); // Error: "this" not available yet
super(processedName, age);
}
}
// ✅ CORRECT: Process arguments before constructor, or pass directly
class User extends Person {
constructor(name: string, age: number) {
// Process arguments before calling super()
const processedName = name.trim();
super(processedName, age); // Parent gets processed values
}
}
// ✅ ALTERNATIVE: Use a factory method if complex processing is needed
class User extends Person {
private constructor(name: string, age: number) {
super(name, age);
}
static create(name: string, age: number): User {
const processedName = name.trim();
return new User(processedName, age);
}
}Property initializers that reference "this" can cause this error:
// ❌ WRONG: Property initializer using "this" before super()
class Derived extends Base {
private calculatedValue = this.computeValue(); // Error: "this" in initializer
constructor() {
super();
}
private computeValue(): number {
return 42;
}
}
// ✅ CORRECT: Initialize in constructor after super()
class Derived extends Base {
private calculatedValue: number;
constructor() {
super();
this.calculatedValue = this.computeValue(); // Safe after super()
}
private computeValue(): number {
return 42;
}
}
// ✅ ALTERNATIVE: Use a static method if no instance needed
class Derived extends Base {
private calculatedValue = Derived.computeStaticValue();
constructor() {
super();
}
private static computeStaticValue(): number {
return 42;
}
}TypeScript parameter properties can't reference "this" before super():
// ❌ WRONG: Parameter property with "this" reference
class Derived extends Base {
constructor(private value = this.getDefault()) { // Error: "this" in default
super();
}
private getDefault(): string {
return 'default';
}
}
// ✅ CORRECT: Handle default values differently
class Derived extends Base {
private value: string;
constructor(value?: string) {
super();
this.value = value ?? this.getDefault(); // Safe after super()
}
private getDefault(): string {
return 'default';
}
}
// ✅ ALTERNATIVE: Use undefined check with default parameter
class Derived extends Base {
constructor(private value?: string) {
super();
if (this.value === undefined) {
this.value = this.getDefault();
}
}
private getDefault(): string {
return 'default';
}
}Methods or arrow functions that use "this" and are called before super() will fail:
// ❌ WRONG: Calling method that uses "this" before super()
class Derived extends Base {
constructor() {
this.initialize(); // Error: Method uses "this" internally
super();
}
private initialize(): void {
this.setup(); // Uses "this"
}
}
// ✅ CORRECT: Call methods after super()
class Derived extends Base {
constructor() {
super();
this.initialize(); // Safe after parent is initialized
}
private initialize(): void {
this.setup();
}
}
// ❌ WRONG: Arrow function in parameter default
class Derived extends Base {
constructor(callback = () => this.doSomething()) { // Error: "this" in arrow function
super();
}
}
// ✅ CORRECT: Handle callback initialization differently
class Derived extends Base {
private callback: () => void;
constructor(callback?: () => void) {
super();
this.callback = callback ?? (() => this.doSomething()); // Safe
}
}Ensure your TypeScript configuration handles class initialization correctly:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictPropertyInitialization": true, // Catches uninitialized properties
"noImplicitThis": true, // Catches "this" with implicit 'any' type
"target": "es2015" // or higher for proper class support
}
}Key compiler options:
- strictPropertyInitialization: Ensures instance properties are initialized in constructor
- noImplicitThis: Error when "this" is implicitly 'any'
- useDefineForClassFields: Modern class field semantics (ES2022+)
Run tsc --noEmit to check for type errors after making changes.
## Understanding JavaScript's Class Initialization Order
In JavaScript/TypeScript, class initialization follows this order:
1. Property initializers (ES2022 class fields) execute before the constructor
2. Constructor parameters are evaluated
3. super() must be called before accessing "this"
4. Remaining constructor body executes after super()
This is why property initializers can't use "this" - they run before the parent class is initialized.
## ES2022 Class Fields vs TypeScript's Implementation
TypeScript's class field behavior depends on the useDefineForClassFields compiler option:
- false (default for target < ES2022): Fields are initialized after super() in constructor
- true (ES2022+): Fields are initialized before constructor, following ES spec
With useDefineForClassFields: true, property initializers absolutely cannot use "this" since they run before the constructor.
## Abstract Classes and super()
For abstract classes, you still need to call super() even though there's no concrete parent:
abstract class Base {
abstract initialize(): void;
}
class Derived extends Base {
constructor() {
super(); // Still required for abstract classes
this.initialize();
}
initialize(): void {
// implementation
}
}## Mixins and Multiple Inheritance Patterns
When using mixins or similar patterns, ensure each mixin's initialization happens in the correct order:
function Timestamped<T extends new (...args: any[]) => any>(Base: T) {
return class extends Base {
timestamp = Date.now();
constructor(...args: any[]) {
super(...args); // Must call super() first
// mixin initialization
}
};
}## Performance Considerations
Calling super() early is not just a syntax requirement - it ensures proper memory layout and prototype chain setup. Accessing properties before the parent is initialized can lead to memory corruption or incorrect property lookups in optimized JavaScript engines.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript