formik icon indicating copy to clipboard operation
formik copied to clipboard

Support custom fields without workarounds

Open kneczaj opened this issue 2 years ago • 1 comments

Feature request

Current Behavior

This is the interface of Formik's handle change:

handleChange: {
        /** Classic React change handler, keyed by input name */
        (e: React.ChangeEvent<any>): void;
        /** Preact-like linkState. Will return a handleChange function.  */
        <T = string | React.ChangeEvent<any>>(field: T): T extends React.ChangeEvent<any> ? void : (e: string | React.ChangeEvent<any>) => void;
    };

The Field's Value can be any like here:

export interface FieldInputProps<Value> {
    /** Value of the field */
    value: Value;
    /** Name of the field */
    name: string;
    /** Multiple select? */
    multiple?: boolean;
    /** Is the field checked? */
    checked?: boolean;
    /** Change event handler */
    onChange: FormikHandlers['handleChange'];
    /** Blur event handler */
    onBlur: FormikHandlers['handleBlur'];
}

Just HTML <input> tag produces React.ChangeEvent what means that Formik cannot be attached with its handleChange function to a custom field which does not produce it.

The error in the browser when I use it with e.g. boolean is:

Uncaught TypeError: Cannot read properties of undefined (reading 'type')
    at formik.esm.js:721:1
    at formik.esm.js:755:1
    at formik.esm.js:1256:1
    at n (project44-manifest-ds.js:2:48822)
    at project44-manifest-ds.js:2:49089
    at Object.setSelected (project44-manifest-ds.js:2:222034)
    at onChange (project44-manifest-ds.js:2:221812)
    at Object.vi (react-dom.production.min.js:202:330)
    at ui (react-dom.production.min.js:32:27)
    at xi (react-dom.production.min.js:32:81)

and it happens at this line: https://github.com/jaredpalmer/formik/blob/b9cc2536a1edb9f2d69c4cd20ecf4fa0f8059ade/packages/formik/src/Formik.tsx#L611

Workaround which I currently use is:

  const { values, setFieldValue } = useFormikContext<SearchFormProps>();
  const [field, meta] = useField<string>('test');

  return (
    <CheckboxField
      isSelected={field.value}
      // onChange here has signature of `(value: boolean) => void` 
      onChange={(value) => {
        setFieldValue(field.name, value);
      }}
      ....

Desired Behavior

A boolean should be possible to save with handleChange so I may not use the workaround.

Suggested Solution

Please support signature

handleChange: <Value>(field: Value | React.ChangeEvent<any>) => void

How to do it?

Instead of if (!isString(eventOrTextValue)) check here https://github.com/jaredpalmer/formik/blob/b9cc2536a1edb9f2d69c4cd20ecf4fa0f8059ade/packages/formik/src/Formik.tsx#L600 this could be used:

function isChangeEvent<T extends HTMLElement = HTMLElement>(
  eventOrValue: ChangeEvent<T> | unknown,
): eventOrValue is ChangeEvent<T> {
  return (eventOrValue as ChangeEvent<T>).target !== undefined;
}

Who does this impact? Who is this for?

  • Devs who write custom components without <input> tag
  • devs who use component libraries which does not expose onChange prop with (event: ChangeEvent) => void signature, and expose onChange prop with <T>(val: T) => void signature

Describe alternatives you've considered

The workaround is the alternative.

Additional context

Noting

kneczaj avatar Sep 15 '22 12:09 kneczaj