2

I'm quite new to Typescript and I'm trying to have a conditional extends based on a function argument value. The context is to define a widget for object selection that can be a select, a list of radio (a radio group) or a list of checkbox (a checkbox group). Each of this different widget accept different props, name: SelectProps, RadioGroupProps and CheckboxGroupProps. The widget selection is based on two parameters: expand (means we want a list of checkbox or radio) and multiple (means we want to select multiple elements or not).

function SelectionWidget(props: SelectionWidgetProps) {}

Here, I want that:
if props matches {expand: true, multiple: false} then SelectionWidgetProps must extends RadioGroupProps.
if props matches {expand: true, multiple: true} then SelectionWidgetProps must extends CheckboxGroupProps.
otherwise, SelectionWidgetProps must extends SelectProps.

I tried the following:

type SelectTypeExtend<T> = 
    T extends { expand: false } ? SelectProps : 
    T extends { expand: true; multiple: false } ? RadioGroupProps :
    T extends { expand: true; multiple: true; } ? CheckboxGroupProps :
    never;

interface SelectTypeProps extends SelectTypeExtends {
    multiple?: boolean;
    expand?: boolean;
}

But it doesn't work as SelectTypeExtends expects an argument. Is it possible to achieve this with Typescript? Thanks!

1 Answer 1

1

You can extend interface only with statically known types. It means that does not allow you to extend SelectTypeProps with SelectTypeExtends because SelectTypeExtends is a black box. Nobody know type of T.

In order to manage this case, you should use type instead of interface:

type SelectProps = {
  tag: 'SelectProps'
}

type RadioGroupProps = {
  tag: 'RadioGroupProps'
}

type CheckboxGroupProps = {
  tag: 'CheckboxGroupProps '
}

type SelectTypeExtend<T> =
  T extends { expand: false } ? SelectProps :
  T extends { expand: true; multiple: false } ? RadioGroupProps :
  T extends { expand: true; multiple: true; } ? CheckboxGroupProps :
  never;

type SelectTypeProps<T> = { // <------ type instead of interface
  multiple?: boolean;
  expand?: boolean;
} & SelectTypeExtend<T>

Example with react components:

import React from "react";

import {
  Select,
  SelectProps,
  Checkbox,
  CheckboxGroupProps,
  Radio,
  RadioGroupProps
} from "formik-antd";


export type SelectionComponentProps =
  | { expand?: false } & { options: SelectProps }
  | { expand: true; multiple?: false } & { options: RadioGroupProps }
  | { expand: true; multiple: true } & { options: CheckboxGroupProps };

function SelectionComponent(props: SelectionComponentProps) {
  if (props.expand) {
    if (props.multiple) {
      return <Checkbox.Group {...props.options} />;
    } else {
      return <Radio.Group {...props.options} />;
    }
  }
  return <Select {...props.options} />;
};

const select: SelectProps = 'unimplemented' as any;
const radio: RadioGroupProps = 'unimplemented' as any;
const checkbox: CheckboxGroupProps = 'unimplemented' as any;

export const TestRadio = () => {
  // Accept properties from RadioGroupProps but not from SelectProps or CheckboxGroupProps
  return [
    <SelectionComponent expand options={radio} />, // ok
    <SelectionComponent expand options={select} />, // expected error
    <SelectionComponent expand options={checkbox} />, // expected error
  ]
};

export const TestCheckbox = () => {
  // Accept properties from CheckboxProps but not from SelectProps or RadioGroupProps
  return [
    <SelectionComponent expand multiple options={checkbox} />, // ok
    <SelectionComponent expand multiple options={select} />, // expected error
    <SelectionComponent expand multiple options={radio} /> // expected error
  ]
};

export const TestSelect = () => {
  // Accept properties from SelectProps but not from RadioGroupProps or CheckboxGroupProps
  return [
    <SelectionComponent options={select} />, // ok
    <SelectionComponent options={radio} />, // expected error
    <SelectionComponent options={checkbox} />, // expected error
  ]
};

Playground

I have slightly modified SelectionComponentProps type. Please keep in mind, that if you don't provide some property to react component, TS treats it as undefined and not as false, hence you need to make some of your false properties as a partial

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

5 Comments

Thank you very much. The last part I'm missing, is how to define my function signature with this? ``` const SelectType = ({expand = false, multiple = false, ...props}: SelectTypeProps) => {} ```
@Vincent please provide more explanation. How do you want call this function and what this function should return. Do you want to pass {multiple?: boolean; expand?: boolean;} type as an argument to the function and return one of *GroupProps? Please share all three types *GroupProps
This function is a react functional component and return a React.Node. The types I want to extend are defined here: github.com/jannikbuschke/formik-antd/blob/master/src/checkbox/…. github.com/jannikbuschke/formik-antd/blob/master/src/radio/…. github.com/jannikbuschke/formik-antd/blob/master/src/select/…. const SelectType = (props: SelectTypeProps) => {} I want to call it as a regular react component, like: <SelectType multiple expand {...otherPropsCompatiblesWithCheckboxGroup} />
@Vincent could you please still provide a minimum reproducible example? I think you can use a union SelectProps | RadioGroupProps | CheckboxGroupProps in this case instead of conditional types, but I'm not sure
Here is a codesandbox to better understand what I'm trying to achieve: codesandbox.io/s/sad-dewdney-3gv08

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.