Argument of type 'interface' is not assignable to parameter of type 'interface'

profile
Tim Deschryver
timdeschryver.dev

While I was working on a refactor, in the NgRx project, to allow N number of action handlers on within an NgRx reducer createReducer - huzzah for variadic tuple types 🥳, I've encountered a TypeScript issue that I've already bumped against to.

The issue is a compile-time error that gives us the message "Argument of type 'interface' is not assignable to parameter of type 'interface'". When you don't read the full error message (like I did) it can take a while to understand the problem, why it happens, and how to solve this error.

From my understanding, the error pops up when there's a wrapper method that:

The snippet below illustrates the problem in the most simple way.

These functions give us the following results when we use them:

Now the weird part is that when the callback method accepts an input parameter of the same type, it does compile. But only when the argument is used.

From what I could see, is that somehow TypeScript isn't able to correctly infer the generic's interface anymore. To be honest with you, I don't know why and I would expect this to compile because the signature has the same types.

As shown in the usage examples above, we can make this work but therefore we must make changes to the signature or the way how the callback method is invoked. From a consumer's perspective, this is bad and this would've been a massive breaking change for the ones using NgRx.

As a fix, I introduced a new generic in the NgRx to help TypeScript with the interference. While at first this seemed like a fix, it introduced a hidden breaking change because the signature of the on method was changed.

format_quote

Noteworthy to mention that if you're adding a generic to only use it once, you're probably doing something wrong. This rule "Type Parameters Should Appear Twice" is explained in The Golden Rule of Generics, written by Dan Vanderkam.

Luckily, Alex Okrushko provided a better solution that doesn't impact the consumer.

The solution to resolve this issue correctly is to help TypeScript to infer the type. For this, we can tweak the strictness of the generic. Instead of using the generic directly we can create a mapped type by using keyof.

The other strange part here is that in the NgRx types, Alex didn't need to type the generic's properties as potentially undefined (as you can see here) while I had to when I created this reproduction.

So while this blog post does leave us with some unclarity, it does provide a solution to this problem. To play around with this reproduction, see the TypeScript Playground link

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post