skip to Main Content

I have a React Component that takes up an object which returns the input component to be rendered. But right now there is no strict type checking and that is the problem. Check the example below:

const FieldRenderer = (fieldProps: any) => {
  // implementation here to be ignored
}

const fieldProps = {
  id: 'gender',
  label: 'Gender',
  type: 'dropdown',
  options: ['Male', 'Female']
}
<FieldRenderer fieldProps={fieldProps}/>

But the problem here is that even if you pass something like checked: true (which is applicable to a checkbox) or maxLength: 100 (which is for a textbox) the component won’t show an error and I want all the other properties to be typed according to the type property in the object.

Note: There is one problem though, the props are passed in the same way in the entire code base so I won’t be able to change it, but need to have a type check in place.

I need the following to be true

const FieldRenderer = (fieldProps: SomeTypehere) => {
  // implementation here to be ignored
}

const fieldProps = {
  id: 'gender',
  label: 'Gender',
  type: 'dropdown',
  checked: true
}
<FieldRenderer fieldProps={fieldProps}/> // Error here as dropdown doesn't need a checked property but also needs a options property

Thanks in advance for the help !

2

Answers


  1. You can use a discriminated union for the type of fieldProps (in this case, using type as the discriminant):

    type CommonFieldRendererProps = {
        id: string;
        label: string;
    };
    
    type CheckboxFieldRendererProps = CommonFieldRendererProps & {
        type: "checkbox";
        checked: boolean;
    };
    
    type DropdownFieldRendererProps = CommonFieldRendererProps & {
        type: "dropdown";
        options: string[];
    };
    
    type FieldProps = 
        | CheckboxFieldRendererProps
        | DropdownFieldRendererProps
        // ...
        ;
    type FieldRendererProps = {
        fieldProps:  FieldProps;
    }
    
    const FieldRenderer = (fieldProps: FieldRendererProps) => {
        // implementation here to be ignored
        return <div/>;
    };
    

    Notice that the type of type isn’t string, it’s the string literal type "checkbox" for the checkbox props or "dropdown" for the dropdown props.

    It works best when you’re providing the props inline, because you don’t have to provide an explicit type (or an as const):

    // Error as desired:
    const bad = <FieldRenderer fieldProps={{
        id: "gender",
        label: "Gender",
        type: "dropdown",
        checked: true,
    }}/>;
    
    // Works
    const good = <FieldRenderer fieldProps={{
        id: "gender",
        label: "Gender",
        type: "checkbox",
        checked: true,
    }}/>;
    

    …but you can also do it with explicit types:

    // Error as desired:
    const badFieldPropsA: DropdownFieldRendererProps = {
        id: "gender",
        label: "Gender",
        type: "dropdown",
        checked: true, // <== Error here (would be on `type` if you used `CheckboxFieldRendererProps` instead)
    };
    const badA = <FieldRenderer fieldProps={badFieldPropsA}/>;
    
    // Works
    const goodFieldPropsA: CheckboxFieldRendererProps = {
        id: "gender",
        label: "Gender",
        type: "checkbox",
        checked: true,
    };
    const goodA = <FieldRenderer fieldProps={goodFieldPropsA}/>;
    

    …or with as const:

    // Error as desired
    const badFieldPropsB = {
        id: "gender",
        label: "Gender",
        type: "dropdown",
        checked: true,
    } as const;
    const badB = <FieldRenderer fieldProps={badFieldPropsB}/>;
    
    // Works
    const goodFieldPropsB = {
        id: "gender",
        label: "Gender",
        type: "checkbox",
        checked: true,
    } as const;
    const goodB = <FieldRenderer fieldProps={goodFieldPropsB}/>;
    

    Playground link

    Login or Signup to reply.
  2. The simplest solution is to just give the props a union type:

    type Props = {
        id: string;
        label: string;
        // other common properties
    } & (
        {
            type: 'dropdown'
            options: string[]
        } | 
        {
            type: 'checkbox',
            checked: boolean
        } // more
    )
    
    const fieldProps: Props = { // Error
      id: 'gender',
      label: 'Gender',
      type: 'dropdown',
      checked: true
    }
    const fieldProps2: Props = {
      id: 'gender',
      label: 'Gender',
      type: 'dropdown',
      options: ['Male', 'Female']
    }
    const fieldProps3: Props = { // Error
      id: 'gender',
      label: 'Gender',
      type: 'checkbox',
    }
    const fieldProps4: Props = {
      id: 'gender',
      label: 'Gender',
      type: 'checkbox',
      checked: true
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search