Romasik
Romasik

Reputation: 21

Required and optional properties based on another property

I am creating a function that can take either control props or value, but I want the function to be able to always take all the props and decided inside which props to choose based on isControlledManually boolean value. But this doesn't work:

1st issue is that it doesn't let me use isControlled manually as boolean, it forces me to decide either false or true, but I can't pass boolean to that component. Exact error is: type 'boolean' is not assignable to type 'false'

2nd issue is even if I decide to pass only false jsut for debugging, then it yells at me, that formControl, fieldPath, selectedNonRhfValue can't be undefined

I understand both of these problems from TypeScript perspective, but how can I solve them?

export type Manual<
FormState extends FieldValues,

  ItemType,
  ItemValue extends string | number
> = SelectFieldQueryProps<ItemType> &
  SelectFieldCommonProps<ItemType, ItemValue> & {
    isControlledManually: true;
    selectedNonRhfValue: ItemValue;
    disabled?: boolean;
    readonly?: boolean;
    readonlyLabel?: string;
    onChange?: (item: ItemType | null) => void;
    fieldPath?: FieldPath<FormState>;
    formControl?: Control<FormState>;
    rules?: Omit<RegisterOptions<FormState>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
  };

export type NotManual<
  FormState extends FieldValues,
  ItemType,
  ItemValue extends string | number
> = SelectFieldQueryProps<ItemType> &
  SelectFieldCommonProps<ItemType, ItemValue> & {
    isControlledManually: false;
    selectedNonRhfValue?: ItemValue;
    disabled?: boolean;
    readonly?: boolean;
    readonlyLabel?: string;
    onChange?: (item: ItemType | null) => void;
    fieldPath: FieldPath<FormState>;
    formControl: Control<FormState>;
    rules?: Omit<RegisterOptions<FormState>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
  };

The component is written like this without any typescript error.

export function SelectField<FormState extends FieldValues, ItemType, ItemValue extends string | number>(
  props: Manual<FormState, ItemType, ItemValue> | NotManual<FormState, ItemType, ItemValue>
) {
  if (props.isControlledManually === true) {
    return <SelectFieldInput {...props} value={props.selectedNonRhfValue} disabled={true} />;
  }

  return <ControlledSelectField {...props} />;
}

The error is shown when I try to use the prepared component anywhere else, for example like this and when I do the I get the errors when passing props mentioned above.

export function QueryField<FormState extends FieldValues, ItemType, ItemValue extends string | number>(
  props: Omit<
    Manual<FormState, ItemType, ItemValue> | NotManual<FormState, ItemType, ItemValue>,
    'data' | 'isLoading' | 'isLoadingError'
  > & {
    queryResult: QueryResult<ItemType[]>;
    dataFilter?: (item: ItemType) => boolean;
  }
) {
  const getData = () => props.queryResult.data || [];
  const getFilteredData = () => (props.dataFilter ? getData().filter(props.dataFilter) : getData());

  return (
    <SelectField
      {...props}
      data={getFilteredData()}
      isLoading={props.queryResult.isLoading}
      isLoadingError={props.queryResult.isError}
    />
  );
}

Upvotes: 1

Views: 75

Answers (1)

Romasik
Romasik

Reputation: 21

I found out that the issue was the whole time inside the

Omit<Manual<FormState, ItemType, ItemValue> | NotManual<FormState, ItemType, ItemValue>,'data' | 'isLoading' | 'isLoadingError'>

It turns out, you cant use Omit with unions types. You need to use custom DistributiveOmit, that can distribute across union types.

export type DistributiveOmit<T, K extends string> = T extends unknown ? Omit<T, K> : never;

Upvotes: 0

Related Questions