2

I want to make my props be either type A, or B. For example

export default function App() {
  type Checkbox = {
    type: "checkbox";
    checked: boolean;
  };

  type Dropdown = {
    type: "dropdown";
    options: Array<any>;
    selectedOption: number;
  };

  type CheckboxOrDropdown = Checkbox | Dropdown;

  const Component: FC<CheckboxOrDropdown> = (props) => {
    return <>"...correct component"</>;
  };

  // these returns are just examples
  return <Component type="checkbox" checked={true} />;
  return <Component type="dropdown" options={[]} selectedOption={0} />;
}

Here's a fiddle

How can I achieve the same, but without the "type" prop? So that TS recognizes the type based on other props?

4
  • Does this work for you ? However, I think that this version is less verbose Commented Jan 17, 2022 at 13:21
  • So you want a discriminated union but without the discriminant? Commented Jan 17, 2022 at 13:23
  • @captain-yossarian huh, I actually like the second option. Is there a way to make it this readable not using FC? Commented Jan 17, 2022 at 13:28
  • @jonrsharpe basically. By the tone of the question, I'm assuming it's not really doable/is a bad practice? ;D Commented Jan 17, 2022 at 13:29

1 Answer 1

1

You can overload your component. By overloading here I mean intersection of two functional components:

import React, { FC } from 'react'

export default function App() {
  type Checkbox = {
    checked: boolean;
  };

  type Dropdown = {
    options: Array<any>;
    selectedOption: number;
  };


  const Component: FC<Checkbox> & FC<Dropdown> = (props) => {
    return <>"...correct component"</>;
  };

  return [<Component checked={true} />, <Component  options={[]} selectedOption={0} />];
}

This is the less verbose version I know.

If you have a lot of component types and you don't want to manually intersect them, you can use distributivity.

import React, { FC } from 'react'

export default function App() {
  type Checkbox = {
    checked: boolean;
  };

  type Dropdown = {
    options: Array<any>;
    selectedOption: number;
  };

  // credits goes to https://stackoverflow.com/a/50375286
  type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
  ) => void
    ? I
    : never;

  type Overload<T> = UnionToIntersection<T extends any ? FC<T> : never>

  const Component: Overload<Checkbox | Dropdown> = (props) => {
    return <>"...correct component"</>;
  };

  return [<Component checked={true} />, <Component options={[]} selectedOption={0} />];
}

Playground

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

1 Comment

Makes sense. Thanks!

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.