How do I pass a generic value type accessed through an index in TypeScript?

Viewed 178

I"m trying to pass a generic value to a type accessed through an index, but I'm running into a syntax error.

I've got a function type signature named onCreateNode defined on an interface named GatsbyNode:

interface GatsbyNode {
  ...
  onCreateNode?<TNode extends object = {}>(
    args: CreateNodeArgs<TNode>,
    options?: PluginOptions,
    callback?: PluginCallback
  ): void
  ...

I've now created a function that is also named onCreateNode and I want to assign it the type of GatsbyNode['onCreateNode'] while passing a value to the TNode generic:

export const onCreateNode: GatsbyNode['onCreateNode']<AGenericTypeIWantToPassIn> = ...

However, I get a syntax error at the first angle bracket (<) telling me that this is not valid TypeScript.

1 Answers

You are trying to convert a generic function type to a generic type referring to a function. These are two fundamentally different sorts of generics, and although they are related, the compiler doesn't really give you a direct mechanism to convert between them. See this answer for a fuller explanation; this question is essentially asking for what the other question is asking for.

Anyway, the most straightforward way for you to proceed is to just rewrite the type definition in the desired form yourself:

type GatsbyNodeSpecific<T extends object = {}> = {
  onCreateNode?(args: CreateNodeArgs<T>, options?: PluginOptions, callback?: PluginCallback): void;
}

export const onCreateNode: GatsbyNodeSpecific<AGenericTypeIWantToPassIn>['onCreateNode'] = null!

This will definitely work, but is potentially tedious and possibly error-prone if the upstream definition of GatsbyNode changes.


In the other answer, I show a hacky method to convince the compiler to take a generic function type and convert it to a generic type. For your case it would look like this:

class GenTypeMaker<T extends object = {}> {
  getGatsbyNodeSpecific!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
  gatsbyNodeSpecific = this.getGatsbyNodeSpecific!(
    null! as NonNullable<GatsbyNode["onCreateNode"]>)<T>()
}
type OnCreateGatsbyNodeSpecific<T extends object> = 
  GenTypeMaker<T>['gatsbyNodeSpecific'] | undefined;

And then you can use this new type like this:

export const onCreateNode2: OnCreateGatsbyNodeSpecific<AGenericTypeIWantToPassIn> = onCreateNode;

This is the same type as before. But I doubt anyone reviewing the code above with a phantom class would be happy with it, and the straightforward-if-redundant method of rewriting the definition manually is probably the better way to go.

Okay, hope that helps; good luck!

Playground link to code

Related