john_law
john_law

Reputation: 35

Guarding for undefined in union type not working

I'm getting the following error for the code below, which I suspect might not be the expected behavior. Basically TS is complaining that rules[name] might not be callable, which is true because it can be undefined, but if I guard that case it still throws the same error. I think problem becomes clear by reading the comments in the code.

This expression is not callable.
  Not all constituents of type '((value: string | number) => boolean) | undefined' are callable.
    Type 'undefined' has no call signatures.ts(2349)
type FieldValues = Record<string, string | number>;
type FieldName<FormValues> = keyof FormValues;
type FormState<FormValues> = {
  values: FormValues,
  errors: Record<FieldName<FormValues>, boolean>,
  focused: Record<FieldName<FormValues>, boolean>,
  touched: Record<FieldName<FormValues>, boolean>,
  dirty: Record<FieldName<FormValues>, boolean>,
  isValid: boolean,
  isDirty: boolean,
};

export function useForm<FormValues extends FieldValues> (
  defaultValues: FormValues,
  rules?: Partial<Record<FieldName<FormValues>, (value: string | number) => boolean>>,
) {
...

  const validate = (name: FieldName<FormValues>, value: string | number) => {

    // either "if (rules && rules[names])" or "if (rules && rules[name] instanceof Function)" 
    // should narrow out undefined thus guaranteeing the correct call signature
    if (rules && rules[name] instanceof Function) {
      setFormState((prevFormState: FormState<FormValues>) => ({
        ...prevFormState,
        errors: {
          ...prevFormState.errors,
          [name]: rules[name](value),
        },
        isValid: isFormValid({
          ...prevFormState.errors,
          [name]: rules[name](value),
        }),
      }))
    }
  }

...
}

I found what I think is a similar issue which has been reported as a bug:

stackoverflow issue

issue on the Typescript repo

But I cannot make sense of it: i) does this fall under the same type of issue and ii) what is the workaround for this?

Edit: changed the title from 'Guarding for function in union type not working' to 'Guarding for undefined in union type not working' as it describes the problem more accurately.

Upvotes: 1

Views: 313

Answers (2)

Max Yankov
Max Yankov

Reputation: 13297

Unless I'm not seeing something here, wouldn't a simple condition like rules && rules[name] work? Typescript compiler already knows it's either function or undefined, so if you check that it's truthy, it should be enough.

Upvotes: 1

john_law
john_law

Reputation: 35

For anyone facing a similar problem a good explanation for why TS has problems with these and a solution for it is provided here.

So what I ended up doing was:

   if (rules) {
      const fieldRule = rules[name]

      if (fieldRule) {
        setFormState((prevFormState: FormState<FormValues>) => ({
          ...prevFormState,
          errors: {
            ...prevFormState.errors,
            [name]: fieldRule(value),
          },
          isValid: isFormValid({
            ...prevFormState.errors,
            [name]: fieldRule(value),
          }),
        }))
      }
    }

Upvotes: 0

Related Questions