react-final-form
react-final-form copied to clipboard
Pristine and dirty shouldn't change between wizard pages.
Are you submitting a bug report or a feature request?
Bug report
What is the current behavior?
- Fill first and last name (
pristine
should change tofalse
) - Click next
-
pristine
back totrue
- Back to previous page
-
pristine
is stilltrue
What is the expected behavior?
pristine
and dirty
shouldn't change between wizard pages.
or:
There should be other way, to check state of fields that are not currently in the DOM.
Sandbox Link
https://codesandbox.io/s/wizard-field-state-gfevw?file=/Wizard.js
What's your environment?
n/a
Other information
I have multi-paged form (without validate
on each page) and I want to block submitButton, when nothing has changed.
We also got surprised by this behaviour that field values are persisted but the meta state is lost once your field unmounts from the DOM.
This is caused by fields having no active subscribers. Our workaround is that we mount extra <Field>
component for each field we have on the same level as the <Form>
element ensuring there is always an active subscriber for our fields.
The solution is hacky and error-prone though, and it can get quite complex if you have arrays or nested arrays of fields like we do.
We personally found the way final-form deals with meta state quite confusing. It would be nice if it was at least configurable that meta state is persisted even if you don't have active subscribers, for big and complex forms it's often the case that you don't have all the fields mounted at all times.
@erikras do you think there's any chance that something is done about this in the library itself?
The way the Wizard is implemented it is keeping each page as a separate form, and the combining only the values in the parent component. This solves many problems like forcing validation when hitting "Next", only marking the fields on the page as touched
when validation halts submit, etc.
You could potentially raise the <Form>
component up into the parent and then do some sort of conditional validation for each page, but then you've got a whole myriad of other problems to deal with.
do you think there's any chance that something is done about this in the library itself?
No.
We ran into a similar situation, where we had some fields inside an accordion. When an accordion item is closed, its child components become unmounted, and therefore many of the Field
components in the form become unmounted.
Our app is required to automatically save the form when we leave the page, but only if the form is dirty. To do this, we check the dirty
state of the form state. Because the dirty
state was getting reset back to false
when an accordion item was getting closed, the save was not occurring in these situations when it should have.
Are there any recommendations to work around this issue? The best idea I could come up with was to track the dirty
state ourselves. Which is unideal, because now we have two versions of dirty
, and we need to ensure that future developers don't use the wrong value.
What we ended up actually doing was creating a fake Field
component, which lived beside the accordion. It would watch for form changes, and set its own dirty
field state to true
when a change is made. So when an accordion item closes, this fake Field
would never become unmounted, and therefore the overall form's dirty
state won't get set back to false
.
I am also having issues with an Accordion-like component. To get around the metadata getting dropped, I am hiding the fields rather than unmounting them when the accordion is closed. But that creates performance issues when initializing such a form with 100s of elements in the accordion.
Not sure how performant this is, but I have been using the below FormSpy
component at the root of the Form to keep modified fields mounted.
<FormSpy subscription={{ dirtyFields: true }}>
{({ dirtyFields }) =>
// This keeps dirty fields mounted between pages.
Object.keys(dirtyFields).map((name, idx) => (
<Field
key={idx}
name={name}
subscription={{}}
render={() => null}
></Field>
))
}
</FormSpy>
This has been working pretty well for our wizard forms.
I am facing the same issue and it is disappointing that nothing will be done from library side given that it is a very common scenario with multi-tab, wizard forms of today.😞
same here, any workarounds?
I am stuck in this too...
I also have the same problem. My workaround (not flexible, but it's ok for simple cases):
export const getFormDirtyFields = <FormState extends Record<string, any>>({
initialValues,
values,
}: {
initialValues: FormState;
values: FormState;
}): { [k in keyof FormState]?: boolean } => {
const dirtyFields: Record<string, boolean> = {};
Object.entries(values).forEach(([key, value]) => {
if (JSON.stringify(value) !== JSON.stringify(initialValues[key])) {
dirtyFields[key] = true;
}
});
return dirtyFields;
};
<FinalForm
initialValues={initialValues}
render={({ values }) => {
const dirtyFields = getFormDirtyFields({ initialValues, values });
return <FormFields dirtyFields={dirtyFields} />;
}}
/>
Also, I got that keepDirtyOnReinitialize is broken too with few tabs...
Not sure how performant this is, but I have been using the below
FormSpy
component at the root of the Form to keep modified fields mounted.<FormSpy subscription={{ dirtyFields: true }}> {({ dirtyFields }) => // This keeps dirty fields mounted between pages. Object.keys(dirtyFields).map((name, idx) => ( <Field key={idx} name={name} subscription={{}} render={() => null} ></Field> )) } </FormSpy>
This has been working pretty well for our wizard forms.
It works very well for my use case, thank you 🙏
The way the Wizard is implemented it is keeping each page as a separate form, and the combining only the values in the parent component. This solves many problems like forcing validation when hitting "Next", only marking the fields on the page as
touched
when validation halts submit, etc.
@erikras which wizard is this? The wizard example here seems to have just one Form
, not one per page?