How do I override the defaultValues in useForm and maintain the isDirty function?

0

Issue

I have a situation where I need to call my DB to obtain some default values. But in the off-chance the data does not exist, I will set some default values in my useForm. Basically, this means that the defaultValues declared in useForm is a fallback value if I fail to obtain the default values from the DB.

From what Im understanding, according to the documentation with regards to useForm,

The defaultValues for inputs are used as the initial value when a component is first rendered, before a user interacts with it.

Or basically, the useForm is one of the first things defined when the page is loaded.

So, unless I can call my DB before useForm is loaded, Im a little stuck on this.

I’ve read that each Controller field can have something called defaultValue which sounds like the solution, but the documentation mentioned a caveat of

If both defaultValue and defaultValues are set, the value from defaultValues will be used.

I considered setValues but I want to use the isDirty function, which allows field validation and the value used to check whether the form is dirty is based on the useForm defaultValues. Thus, if I were to use setValues, the form would be declared dirty, which is something I do not want.

TL;DR this is what I want:

This is my initial value(the fallback value, result A).

const { formState: { isDirty }, } = useForm(
        {defaultValues:{
            userID: "",
            userName: "",
            userClass: "administrator",
        }}
    );

What I want to do is to make a DB call and replace the data, so that it would now look something like this(result B) if the call is successful(if fail, it will remain as result A).

const { formState: { isDirty }, } = useForm(
        {defaultValues:{
            userID: "1",
            userName: "user",
            userClass: "administrator",
        }}
    );

Please note that the DB call will replace only the userID and userName default values, the default value for userClass will be maintained.

So, the flow is as such:

Case 1: render form -> result A -> DB call -> success -> result B

Case 2: render form -> result A -> DB call -> fail/no data -> result A

So, unless I actually key in an input that is different from the default values of either results depending on the case, both Case 1 and Case 2 should return isDirty==false when I check it.

Solution

For react-hook-form@7.22.0 and newer

I think you want to use reset here in combination with useEffect to trigger it when your DB call has finished. You can additionally set some config as the second argument to reset, for example affecting the isDirty state in your case.

Although your answer works there is no need to use getValues here, as reset will only override the fields which you are passing to reset. Your linked answer is for an older version of RHF, where this was necessary.

Also if you’re not adding a field dynamically on runtime then you can just pass the whole object you get from your DB call to reset and set the shouldUnregister to true in your useForm config. This way props from your result object which haven’t got a corresponding form field will get ignored.

export default function Form() {
  const { register, handleSubmit, reset, formState } = useForm({
    defaultValues: {
      userID: "",
      userName: "",
      userClass: "administrator"
    },
    shouldUnregister: true
  });

  console.log(formState.isDirty);

  const onSubmit = (data) => {
    console.log(data);
  };

  const onReset = async () => {
    const result = await Promise.resolve({
      userID: "123",
      userName: "Brian Wilson",
      otherField: "bla"
    });

    reset(result);
  };

  useEffect(() => {
    onReset();
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>User ID</label>
      <input type="text" {...register("userID")} />

      <label>User Name</label>
      <input type="text" {...register("userName")} />

      <label>User Class</label>
      <input type="text" {...register("userClass")} />

      <input type="submit" />
    </form>
  );
}

Here is a Sandbox demonstrating the explanation above:

Edit react-hook-form-reset-v7 (forked)

For older versions

Just merge your defaultValues or field values via getValues with the result of your DB call.

export default function Form() {
  const {
    register,
    handleSubmit,
    reset,
    formState,
    getValues,
    control
  } = useForm({
    defaultValues: {
      userID: "",
      userName: "",
      userClass: "administrator"
    },
    shouldUnregister: true
  });

  console.log(formState.isDirty);

  const onSubmit = (data, e) => {
    console.log(data);
  };

  const onReset = async () => {
    const result = await delay();

    reset({ ...getValues(), ...result });
  };

  useEffect(() => {
    onReset();
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>User ID</label>
      <input type="text" {...register("userID")} />

      <label>User Name</label>
      <input type="text" {...register("userName")} />

      <label>User Class</label>
      <Controller
        name="userClass"
        control={control}
        render={({ field }) => <input type="text" {...field} />}
      />

      <input type="submit" />
    </form>
  );
}

Edit react-hook-form-reset-v7 (forked)

Answered By – knoefel

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More