'React-hook-form doesn't set isDirty to false when back to initial state
In the official exemple for controlled inputs , if you modify the value of an input and then change it back to its initial value, isDirty
will be set to true but won't be set back to false and dirtyField
will contain the touched field.
In an older exemple with uncontrolled inputs, we dont have quite same behaviour. In fact, if you modify the value of an input and then change it back to its initial value, isDirty
will still be falsy but dirtyFields
will not contain the touched field.
Shouldn't isDirty
be set back to false when the form is back to its initial state and dirtyFields
should be empty?
Is it the intended behaviour?
Does the Controllers break the formState?
Solution 1:[1]
isDirty
is based on the form input values against default values.
https://react-hook-form.com/api#formState
Make sure to provide all inputs' defaultValues at the useForm, so hook form can have a single source of truth to compare whether the form is dirty.
Here is an example with uncontrolled inputs: https://codesandbox.io/s/bold-kapitsa-7m6o0?file=/src/App.tsx
example with controlled inputs: https://codesandbox.io/s/dark-framework-op8jy?file=/src/App.tsx
Solution 2:[2]
Two years late, but adding to what Bill answered and if anyone has a similar issue (like I did). Not only does it need default values on the useForm, there can't be additional properties on the default values object that aren't on the form. The issue I had was that my form took a merge of the default values (for a new entry) and the data object (for editing an existing entry). So if you had something like this
const person = {id: 123, firstName: "Jane", lastName: "Smith"};
and your form was used for editing and creating, the component could have a optional prop for "person" and useForm is setup like this
const formMethod = useForm({
defaultValues: person || {firstName: "", lastName: ""},
});
I didn't add "id" to the form, because it wasn't ever going to be edited and you don't have an id for create, and the value was available for the update method. Maybe that's an anti-pattern, but it's what was done.
When the form is first rendered, isDirty will be false. When you change a value, isDirty will be true. When you change back to the original value, isDirty remains true, because ALL of the default values don't match the form input values and never will in this instance.
This is a simplistic example where the id could be added to the form as a hidden input or registered or something, but for a real solution, there could be lots of other fields that are coming down the api that aren't editable and shouldn't (or couldn't) be part of the form. Takeaway is this - make sure that your editing model matches your default values model. I used this to merge the objects, remove nulls, patch in missing props and remove props that weren't on the default. "mergeWith" and "pick" are lodash methods.
function mergeFormData(defaults: Object, data?: Object, removeExtras?: boolean) {
if (!data) return defaults;
// This should merge everything on data, and take
// defaults where applicable.
// It's only shallow merge, and it uses the "data" if it isn't null or undefined.
// ?? should do it, so it will allow "" and 0 to be kept on the data object
// Use removeExtras to take out props from the data object
// that aren't on the default object
return mergeWith({},
removeExtras ? pick(data, Object.keys(defaults)) : data,
defaults,
(a, b) => {return a ?? b;});
}
And usage (in my case) for add/edit.
const formContext = useForm<AddressFormData>({
defaultValues: mergeFormData(defaultAddress, address, true),
resolver: yupResolver(addressSchema),
mode: 'onBlur'
});
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Bill |
Solution 2 | Jarrod McGuire |