Search code examples
javascriptreactjstypescriptformikcustom-fields

How to use useField (Formik) properly in Typescript?


I have a error when try to pass all props (type, placeholder, className,...etc) from my <MyTextInput />, how could I fix it? ** This is my code:**

<MyTextInput
 label="Password:"
 name="password"
 placeholder="Enter your password"
 className="text-pink-600"
/>

This is my custom input component:

import { FieldHookConfig, useField } from 'formik';
import React from 'react';

interface IFieldProps {
  label?: string;
}

const MyTextInput: React.FC<IFieldProps & FieldHookConfig<string>> = ({
  label,
  ...props
}) => {
  const { name } = props;
  const [field, meta] = useField(props);

  // console.log({ field, meta, others });
  return (
    <div className="flex flex-col">
      <label htmlFor={name}>{label}</label>
      <input id={name} type="text" {...field} {...props} /> // error here...
    </div>
  );
};

export default MyTextInput;

** This is error:**

Type '{ ref?: LegacyRef<HTMLInputElement> | undefined; key?: Key | null | undefined; accept?: string | undefined; alt?: string | undefined; autoComplete?: HTMLInputAutoCompleteAttribute | undefined; ... 295 more ...; innerRef?: ((instance: any) => void) | undefined; } | { ...; } | { ...; }' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.
  Type '{ ref?: LegacyRef<HTMLSelectElement> | undefined; key?: Key | null | undefined; autoComplete?: string | undefined; disabled?: boolean | undefined; ... 275 more ...; checked?: boolean | undefined; }' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.
    Type '{ ref?: LegacyRef<HTMLSelectElement> | undefined; key?: Key | null | undefined; autoComplete?: string | undefined; disabled?: boolean | undefined; ... 275 more ...; checked?: boolean | undefined; }' is not assignable to type 'ClassAttributes<HTMLInputElement>'.
      Types of property 'ref' are incompatible.
        Type 'LegacyRef<HTMLSelectElement> | undefined' is not assignable to type 'LegacyRef<HTMLInputElement> | undefined'.
          Type '(instance: HTMLSelectElement | null) => void' is not assignable to type 'LegacyRef<HTMLInputElement> | undefined'.
            Type '(instance: HTMLSelectElement | null) => void' is not assignable to type '(instance: HTMLInputElement | null) => void'.
              Types of parameters 'instance' and 'instance' are incompatible.
                Type 'HTMLInputElement | null' is not assignable to type 'HTMLSelectElement | null'.
                  Type 'HTMLInputElement' is missing the following properties from type 'HTMLSelectElement': length, options, selectedIndex, selectedOptions, and 4 more.ts(2322)
(property) JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>

I don't wanna do this: <input {...field} placeholder={props.placeholder} type={props.type} />


Solution

  • I think the problem stems from the definition of FieldHookConfig:

    export type FieldHookConfig<T> = GenericFieldHTMLAttributes & FieldConfig<T>;
    

    with GenericFieldHTMLAttributes defined as

    export type GenericFieldHTMLAttributes =
      | JSX.IntrinsicElements['input']
      | JSX.IntrinsicElements['select']
      | JSX.IntrinsicElements['textarea'];
    

    Basically TypeScript is warning you that since you are using FieldHookConfig there is always a chance the properties you receive might be select or textarea properties which will not be compatible with input.

    I think the best way around this is to do:

    import { FieldConfig, useField } from 'formik';
    import React from 'react';
    
    interface IFieldProps {
      label?: string;
    }
    
    const MyTextInput: React.FC<IFieldProps & FieldConfig<string> &  JSX.IntrinsicElements['input']> = ({
      label,
      ...props
    }) => {
      const { name } = props;
      const [field, meta] = useField(props);
    
      // console.log({ field, meta, others });
      return (
        <div className="flex flex-col">
          <label htmlFor={name}>{label}</label>
          <input id={name} type="text" {...field} {...props} /> 
        </div>
      );
    };
    
    export default MyTextInput;
    

    this should narrow the type down to only access input properties.