1

When I instantiate a generic React component, the TypeScript type checker doesn't require me to specify the type argument. It instead defaults to unknown:

snippet

Ideally I would want TypeScript to complain here and ask for the explicit type argument so that I don't forget to pass it in somewhere in the codebase.

Is there a way to achieve that?

6
  • 1
    Can you please explain what you want to do with the generic type? It will help to better understand the problem. Commented Dec 8, 2023 at 18:33
  • What have you tried? I usually just do <SomeComponent<MyType> /> and it works. Commented Dec 8, 2023 at 18:34
  • 1
    You can let it default to another type and check if T is the default, and then force an error: tsplay.dev/WzQE3m Commented Dec 8, 2023 at 19:15
  • @apokryfos that does work. What I'm asking is, is it possible to make TypeScript error when the <MyType> part is omitted. Commented Dec 8, 2023 at 21:07
  • @katniss that works actually. Although it's one of the most unusual pieces of code I've ever seen. I don't think I can get that past code review honestly. But thanks for sharing the knowledge anyway. Commented Dec 8, 2023 at 21:09

1 Answer 1

3

I'm going to post this answer anyways in case anyone else is interested or wants to understand the process behind this. You mentioned that it probably wouldn't get past code review, and I don't think there is any other solution that actually has this kind of behavior.

The core idea is to have the parameter default to another type. Typically, it'll be either void or never. Personally, I like to use never because it is the "empty" type.

Since the parameter will default to some type, you can just check for that type! If it's the default type, then you need to somehow force an error to appear where they call it, ideally with a helpful message.

function SomeComponent<T = never>(props: { /* regular props here */ } & (
    [T] extends [never] ? { error: "generic parameter T is required" } : {}
)): JSX.Element;
function SomeComponent(props: { /* regular props here */ }) {
    return <div></div>;
}

Breakdown of the shenanigans at play:

  1. Check if T is the default (in this case never
  2. If it is the default, then we need to make an error happen. In this example, I do it by requiring an extra property to be added. Obviously, the consumer shouldn't actually put the property there; it's just there to carry the more helpful error message ("generic parameter T is required") forward.
  3. If it is not the default, then don't do anything as they have passed the parameter. In this example, that means I have to intersect the regular props with {} (effectively doing nothing).
  4. Because props now has the complicated type { /* regular props here */} & (...), there's probably going to be some problems when trying to use it. To get around this, I moved everything into an overload so inside the body of the function, props still has the regular type { /* regular props here */ }. Of course, if you need to use T inside the function, then keep it as it is. Otherwise, I suggest doing the same.

Alright, so how does this work in practice?

const x = <SomeComponent />;         // error!
const y = <SomeComponent<string> />; // okay

It seems to work as expected. However, if someone passes a generic type and that type happens to match the default, then you will still get an error:

const z = <SomeComponent<never> />; // still errors even though one was passed

Hopefully, you picked a default type that doesn't make sense to pass. Unfortunately, I don't think there's a way around this as there is no way to know if T used the default or not.

Playground


¹ The fact that I am using never is special in that it now requires the use of brackets ([T] extends [never]) to disable distribution. If you use something other than never, then T extends ... will probably work.

P.S. I found this related Q&A with some alternatives for you if you don't think this kind of solution can pass code review: TypeScript require generic parameter to be provided

Sign up to request clarification or add additional context in comments.

1 Comment

Upvoted. Thanks for the detailed explanation. I'll keep exploring for a simpler answer for a bit. If I don't find anything I'll accept yours as it definitely answers the question. Thanks for sharing the link as well, didn't realize this was a general TS thing and not specific to React components.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.