67

Previously I used to write like this:

<input className="form-control" name="productImage" type='file' onChange={handleImageUpload} ref={register({ required: true })} />

After the update I have to write like this:

<input className="form-control" type="file" {...register('productImage', { required: true })} />

How do I use onChange={handleImageUpload} on the updated version of React Hook Form? Here is the migration docs

8
  • You don't have to make any changes to the onChange prop in react-hook-form v7.0.0. Commented Apr 3, 2021 at 22:34
  • How can I call handleImageUpload() by using onChange={handleImageUpload) like the first line of code I have shown? Commented Apr 3, 2021 at 22:46
  • onChange={handleImageUpload) should work. Can you share a CodeSandbox if you're running into any issues? Commented Apr 3, 2021 at 23:18
  • Here's my CodeSandbox and the onChange={handleImageUpload} doesn't work in my code. Commented Apr 4, 2021 at 6:14
  • 6
    For those new to this thread, the problem with just adding onChange is that in RHF v7.x calling ...register('name') spreads an object containing {onChange, onBlur, name, ref}, so the custom handler will either be ignored or override RHF functionality. See @Bill's answer for the proper way to deal with this. Commented Dec 3, 2021 at 18:14

10 Answers 10

137

https://github.com/react-hook-form/react-hook-form/releases/tag/v7.16.0

V7.16.0 has introduced this new API for custom onChange.

<input
  type="text"
  {...register('test', {
    onChange: (e) => {},
    onBlur: (e) => {},
  })}
/>
Sign up to request clarification or add additional context in comments.

6 Comments

This is the correct answer given @Joris's solution would override the change handler spread from {...register('name')} which returns {onChange, onBlur, name, ref}, and this could lead to bugs.
@Bill, is it possible to achieve the same with the Controller component?
yes @Kort with the controlled component you can intercept the onChange prop.
What if we want to use the native onChange but also have it do something else?
@Bill intercepting onChange with Controller interferes with the default behavior (filename no longer appears in input[type=file]), so it looks like those are actually mutually-exclusive.
|
48

You just have to move the onChange props after {...register(...)}

const productImageField = register("productImage", { required: true });

return (
    <input
        className="form-control"
        type="file"
        {...productImageField}
        onChange={(e) => {
          productImageField.onChange(e);
          handleImageUpload(e);
     }}
    />
)

(Dec 3 2021) edit: this approach is no longer correct since react-hook-form v7.16.0's changes, see @Bill's answer.

4 Comments

@DiamondDrake are you sure this overrides the onChange function? The example above overrides first the onChange function but then invokes it within our overriden onChange function productImageField.onChange(e);. So, what is it that i don't get here?
@Tanckom I've updated my reply since @ DiamondDrake commented
@Joris, might be good to mention this in your answer as new viewers may get confused that this is not the correct way :-)
@Tanckmon is correct, this solution would override the onChange handler spread from {...register('name')} and could lead to bugs. @Bill's answer seems to be the right one here.
8

In register documentation https://react-hook-form.com/api/useform/register, sample exists on Custom onChange, onBlur section :

// onChange got overwrite by register method
<input onChange={handleChange} {...register('test')} />

// register's onChange got overwrite by register method
<input {...register('test')} onChange={handleChange}/>

const firstName = register('firstName', { required: true })
<input 
  onChange={(e) => {
    firstName.onChange(e); // method from hook form register
    handleChange(e); // your method
  }}
  onBlur={firstName.onBlur}
  ref={firstName.ref} 
/>

So for your case :

const productImageRegister = register("productImage", {required: true})
<input className="form-control"
       type="file"
       {...productImageRegister }
       onChange={e => {
           productImageRegister.onChange(e);
           handleImageUpload(e);
       }} />

1 Comment

thank you this solution is perfect when passing register as an prop to child contain the file input (or if you make custom input file)
7

You can use react-hook-form control

  <Controller
  render={({ field }) => <input onChange={event=>{
      handleImageUpload(event);
      field.onChange(event);
    }} />}
  name="image"
  control={control}
/>

Comments

4

Was stuck with the same problem. For me the problem was that my onChange was above the react-hook-form's {...register} and moving it below the register solved the problem for me!!

1 Comment

not a solution, this prevents you from typing in the form field
1

For me, decoration solution worked

const fieldRegister = register("productImage", {required: true})
const origOnChange = fieldRegister.onChange
fieldRegister.onChange = (e) => {
    const res = origOnChange(e)
    const value = e.target.value
    // do something with value
    return res
}

For field declaration use

<input {...fieldRegister}/>

1 Comment

A little verbose, but this works great for those running on anything earlier than v7.16.0
1

I am using form hook's watch method to capture changes instead of the input's onChange event.

https://react-hook-form.com/api/useform/watch

Comments

1

Here is how you can use the onChange together with React hook form and Controller

<Controller
        name="address"
        control={control}
        render={({ field }) => (
            <TextField
                {...field}
                type="text"
                label="address"
                onChange={(e) => {
                    field.onChange(e);
                    yourCustomChangeHandler(e);
                }}
            />
        )}
    />
<Controller

1 Comment

this solution also works on Select component. thanks!
0

I faced a similar issue recently when migrating to V7. If it can help anybody.

A parent component handling the form was passing down to a wrapper the register function, the wrapper passing it down again to an input that needed debouncing on change.

I called the register formLibraryRef in case I wanted to use a different library later but overall I had to do something like:

const { onChange, ...rest } = formLibraryRef(inputName);

pass the onChange to the function that is itself passed to the native onChange event of the input:

const handleDebouncedChange: (event: React.ChangeEvent<HTMLInputElement>) => void = (
    event: ChangeEvent<HTMLInputElement>,
  ) => {
    onChange(event);
    if (preCallback) {
      preCallback();
    }
    debounceInput(event);
  };

and then pass the rest to the input:

<input
  aria-label={inputName}
  name={inputName}
  data-testid={dataTestId}
  className={`form-control ...${classNames}`}
  id={inputId}
  onChange={handleDebouncedChange}
  onFocus={onFocus}
  placeholder={I18n.t(placeholder)}
  {...rest}
/>

The register section in the docs here: https://react-hook-form.com/migrate-v6-to-v7/ gives a bit more info on how to get the onChange and shows an example for Missing ref.

Comments

0

When using a Controller with a control instance I created a handleOnChange method that essentially extends the onChange method passed by the Control.render method.

Like so:

const SomeForm = () => {
  const { control, getValues } = useForm({
    defaultValues: { foo: 1, bar: 2 },
    mode: "all",
    reValidateMode: "onChange",
    shouldUnregister: false,
  });

  const onChangeHandler = useCallback((onChange: (...event: any[]) => void) => {
    return (...event: any[]) => {
      onChange(...event);

      const formAfterChange = getValues();
      // do what you want after the change happens.
    };
  }, [getValues]);


  return <>
    <Controller
      control={control}
      name={"foo"}
      render={({ value, onChange }) => (
        <Input
          value={value}
          onChange={onChangeHandler(onChange)}
        />
      )}
    />

    <Controller
      control={control}
      name={"bar"}
      render={({ value, onChange }) => (
        <Input
          value={value}
          // if you do some logic before calling onChange
          onChange={(e) => {
            let nextValue;
            if ((e.target.value ?? 0) >= 100) {
              nextValue = e.target.value + 10;
            } else {
              nextValue = e.target.value;
            }
            onChangeHandler(onChange)(nextValue);
          }}
        />
      )}
    />
  </>;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.