Code Review Videos > How I Fixed > How I Fixed: TS2340 Only public and protected methods of the base class are accessible via the ‘super’ keyword

How I Fixed: TS2340 Only public and protected methods of the base class are accessible via the ‘super’ keyword

I got really lucky with the fix for this one, as it had me scratching my head for a good while. The gist of this is that I had two classes – a base class, and a sub class. I wanted to call a method with the same name on the sub class as exists on the base class, but ended up with the TS2340 error.

The fix is down to the way the functions were defined. If your function is defined as a class property (an arrow function) then you’re most likely going to hit the same error I did. So the fix is to use the regular function format.

If that makes no sense, or you’re looking for a little more info, keep reading.

Basic Setup

Here’s some example code to get us started:

class A {
  hello = () => {
    console.log('A');
  }
}

class B extends A {
  hello = () => {
    console.log('B');
  }
}

const a = new A();
a.hello();

const b = new B();
b.hello();Code language: TypeScript (typescript)

Pretty straightforward. Two classes, both define a hello method which console logs out the respective class name as a hardcoded string:

➜  ts-super-example npx tsc src/super-testing.ts && node src/super-testing.js
A
BCode language: JavaScript (javascript)

Call The Base / Parent / Super Class

What I wanted was to be able to call b.hello and get A as output.

How to do this?

Well, call through to the base class, right?

And how do we do that?

Using the super keyword:

class B extends A {
  hello = () => {
    super.hello()
  }
}Code language: TypeScript (typescript)

But this no longer compiles:

error TS2340: Only public and protected methods of the base class are accessible via the 'super' keyword.

This is not the best error message, in my humble opinion.

Firstly, by default, methods are public on TypeScript classes.

Secondly, changing the accessibility modifier doesn’t solve the problem.

protected method ts2340

If we swap the hello method in A to be protected, we can no longer call this method from outside of that class. That’s not what we want.

Equally if we change the code as follows:

class A {
  public hello = () => {
    console.log('A');
  }
}

const a = new A();
a.hello();
Code language: TypeScript (typescript)

Then we have only made the public acccessibility modifier explicit. We haven’t solved the problem because it is implicitly public. Which is to say whether we use the public keyword or not, the method is public.

So… yeah, what gives?

The Fix

As above, the fix is to do with the way in which the hello function is defined.

By sheer chance, I stumbled across another use of super in one of the other projects I have access too that gave away the fix. Thank goodness!

Here’s the change we need to make:

class A {
  hello() {
    console.log('A');
  }
}

class B extends A {
  hello = () => {
    super.hello()
  }
}

const a = new A();
a.hello();

const b = new B();
b.hello();
Code language: TypeScript (typescript)

We have to change line 2 to use the regular function definition syntax. Or to put it another way, line 2 cannot be an arrow function.

Interestingly, line 8 – the sub class definition of the function that overrides the base class function – can be either an arrow function or a regular function. I found that surprising.

However, we will cover this is more below, and you should probably not use an arrow function here:

class A {
  hello() {
    console.log('A');
  }
}

class B extends A {
  hello() {
    super.hello()
  }
}
Code language: TypeScript (typescript)

This code now compiles, runs, and outputs the expected:

➜  ts-super-example npx tsc src/super-testing.ts && node src/super-testing.js
A
ACode language: JavaScript (javascript)

As best I am aware, this is to do with what is actually happening under the hood when the code is compiled from TypeScript down to JavaScript.

If we look at the MDN web docs for Arrow Functions the very first bullet point gives the game away (emphasis mine):

An arrow function expression is a compact alternative to a traditional function expression, with some semantic differences and deliberate limitations in usage:

  • Arrow functions don’t have their own bindings to thisarguments, or super, and should not be used as methods.
  • Arrow functions cannot be used as constructors. Calling them with new throws a TypeError. They also don’t have access to the new.target keyword.
  • Arrow functions cannot use yield within their body and cannot be created as generator functions.
MDN Docs for Arrow function expressions

So, err… whoops.

That actually made basic sense to me… until, that is, I tried out the variant above whereby I was still able to use super inside the B.hello function definition.

For that, I was truly stumped. However ChatGPT offers this by way of explanation:

Over To ChatGPT For Some Sage Advice

In your example, B extends A, and B.hello is an arrow function that calls super.hello(). When you call b.hello(), the arrow function is invoked, and because arrow functions don’t bind this, arguments, or super, the value of this inside the arrow function is actually the value of this in the lexical scope where the arrow function was defined. In this case, this is the instance of B that you created with new B().

When you call super.hello() inside the arrow function, JavaScript looks up the hello method on the prototype chain of the current object (b), which is B.prototype. Because B extends A, B.prototype also has access to the hello method defined on A.prototype. When super.hello() is called, it calls the hello method on the superclass (A.prototype.hello) with this set to the instance of B (b).

So, in summary, even though arrow functions don’t have their own bindings to super, in the specific case where you’re defining a class method on a subclass using an arrow function and calling super inside the method, it works because the super reference is resolved dynamically based on the prototype chain, and the this value is set to the instance of the subclass. However, this is generally not a recommended way of defining class methods, because it can be confusing and harder to reason about than using traditional method syntax.

Wrapping Up

Firstly then, the fix is superficially pretty simple: don’t use arrow functions if using super.

There’s actually a bit more to it than that.

Arrow functions could potentially be less performant than regular functions as a new function is created for every instance of the class.

However, arrow functions make working with this inside methods far easier to reason about. There’s no need to think about bind() or self with arrow functions, which can only be a good thing.

So, as ever with JavaScript, there’s a lot going on under the hood that can be quite confusing. I think this is why I tend not to use classes with JavaScript (or TypeScript) if I have the choice.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.