Home > Net >  Generic Input based on enum type
Generic Input based on enum type

Time:11-03

I would like to create a generic input (in react if its matter), that takes type (came from enum) and relevant inputProps based on the given type. The input will generate the relevant input based on switch case of the type.

Here is the code so far:

StandardInput:

export type StandardInputProps<T extends InputTypes> = {
  type: T;
  inputProps: InputProps<T>;
};

type InputProps<T extends InputTypes> = T extends InputTypes.STRING
  ? StringInputProps
  : T extends InputTypes.BOOLEAN
  ? BooleanInputProps
  : T extends InputTypes.DATE
  ? DateInputProps
  : T extends InputTypes.IMAGE
  ? ImageInputProps
  : T extends InputTypes.NUMBER
  ? NumberInputProps
  : never;

const StandardInput = <T extends InputTypes>(props: StandardInputProps<T>) => {
  const { type, inputProps } = props;
  if (!inputProps) {
    return <></>;
  }

  switch (type) {
    case InputTypes.BOOLEAN:
      return <BooleanInput {...inputProps} />;
    case InputTypes.DATE:
      return <DateInput {...inputProps} />;
    case InputTypes.STRING:
      return <StringInput {...inputProps} />;
    case InputTypes.NUMBER:
      return <NumberInput {...inputProps} />;
    case InputTypes.IMAGE:
      return <ImageInput {...inputProps} />;
    default:
      return <></>;
  }
};

export default StandardInput;

StringInput:

type extentedProps = {
  onChange?: (val: string) => void;
};

export type StringInputProps = Overwrite<TextFieldProps, extentedProps>;

const StringInput = (props: StringInputProps) => {
  const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    if (props?.onChange) {
      props?.onChange(e?.target?.value);
    }
  }, []);

  return <TextField {...props} onChange={onChange} />;
};

export default StringInput;

BooleanInput:

type extentedProps = {
  onChange?: (val: Boolean) => void;
};

export type BooleanInputProps = Overwrite<TextFieldProps, extentedProps>;

const BooleanInput = (props: BooleanInputProps) => {
  const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    if (props?.onChange) {
      props?.onChange(Boolean(e?.target?.value));
    }
  }, []);

  return <TextField {...props} onChange={onChange} />;
};

export default BooleanInput;

The issue is that typescript doesn't infer the right type based on the given type prop. It is recognize the type within each case but the inputProps still are the union of all the possible inputProps.

Here is the error that typescript is yelling about:

Type '{ classes?: Partial<TextFieldClasses> | undefined; className?: string | undefined; style?: CSSProperties | undefined; children?: ReactNode; ... 280 more ...; onChange?: ((val: string) => void) | undefined; } | { ...; } | { ...; } | { ...; } | { ...; }' is not assignable to type 'IntrinsicAttributes & Pick<TextFieldProps, "classes" | "className" | "style" | "children" | "color" | "disabled" | "error" | "fullWidth" | ... 275 more ... | "value"> & extentedProps'.
  Type '{ classes?: Partial<TextFieldClasses> | undefined; className?: string | undefined; style?: CSSProperties | undefined; children?: ReactNode; ... 280 more ...; onChange?: ((val: Boolean) => void) | undefined; }' is not assignable to type 'extentedProps'.
    Types of property 'onChange' are incompatible.
      Type '((val: Boolean) => void) | undefined' is not assignable to type '((val: string) => void) | undefined'.
        Type '(val: Boolean) => void' is not assignable to type '(val: string) => void'.
          Types of parameters 'val' and 'val' are incompatible.
            Type 'string' is not assignable to type 'Boolean'.
    62 |       return <NumberInput {...inputProps} />;

Thanks in advance for the help! :)

I tried multiple ways of defining the interface writing each inputProps separately with the hard coded type and doing union as well as other ways but nothing worked..

CodePudding user response:

It is really hard to achieve this without discriminated unions.

so if your input has kind property and then props of a given type

enum InputKind {
  Boolean = 'boolean',
  Number = 'number'
  Text = 'text'
  ...
}

and your actual types are

type BooleanInput = {
  kind: InputKind.Boolean,
  props: BooleanInputProps
}

type StringInput = {
  kind: InputKind.Text,
  props: StringInputProps
}

type InputProps = BooleanInput | StringInput;

then switch over kind would give you what you are looking for since if you have a case Boolean TS will know to discriminate other types.

CodePudding user response:

First, thank you for taking the time to help me!

I have tried something like this before:

StandardInput:

    export type StandardInputProps =
      | {
          type: InputTypes.STRING;
          inputProps: StringInputProps;
        }
      | {
          type: InputTypes.BOOLEAN;
          inputProps: BooleanInputProps;
        }
      | {
          type: InputTypes.DATE;
          inputProps: DateInputProps;
        }
      | {
          type: InputTypes.IMAGE;
          inputProps: ImageInputProps;
        }
      | {
          type: InputTypes.NUMBER;
          inputProps: NumberInputProps;
        };

And it does fix the issue inside the component, but then I'm trying to use the StandardInput in another component that should render it and ran into the following issue

InlineEditInput:

type extentedProps = {
  isEditing: boolean;
};

export type InlineEditInputProps = StandardInputProps & extentedProps;

const InlineEditInput = (props: InlineEditInputProps) => {
  const { isEditing, type, inputProps } = props;

  const changeHandler = useCallback(() => {}, []);

  const inputRef = useRef();
  let content;

  if (isEditing) {
    content = <StandardInput type={type} inputProps={inputProps} />;
  } else {
    content = '';
  }

  const saveChanges = useCallback(() => {}, []);

  const cancelChanges = useCallback(() => {}, []);

  useEffect(() => {
    if (isEditing) {
      // inputRef.current.focus();
    } else {
    }
  }, [isEditing]);

  return <>{content}</>;
};

And got this error:

TS2322: Type '{ type: InputTypes; inputProps: StringInputProps | BooleanInputProps | DateInputProps | ImageInputProps | NumberInputProps; }' is not assignable to type 'IntrinsicAttributes & StandardInputProps'.
  Type '{ type: InputTypes; inputProps: StringInputProps | BooleanInputProps | DateInputProps | ImageInputProps | NumberInputProps; }' is not assignable to type '{ type: InputTypes.NUMBER; inputProps: NumberInputProps; }'.
    Types of property 'type' are incompatible.
      Type 'InputTypes' is not assignable to type 'InputTypes.NUMBER'.
        Type 'InputTypes.STRING' is not assignable to type 'InputTypes.NUMBER'.
    19 |
    20 |   if (isEditing) {
  > 21 |     content = <StandardInput type={type} inputProps={inputProps} />;
       |                ^^^^^^^^^^^^^
    22 |   } else {
    23 |     content = '';
    24 |   }

The type inffered correctly to be InputTypes and props is the union of all the possible inputProps.

  • Related