react-final-form icon indicating copy to clipboard operation
react-final-form copied to clipboard

Pristine and dirty shouldn't change between wizard pages.

Open czterystaczwarty opened this issue 4 years ago • 12 comments

Are you submitting a bug report or a feature request?

Bug report

What is the current behavior?

  1. Fill first and last name (pristine should change to false)
  2. Click next
  3. pristine back to true
  4. Back to previous page
  5. pristine is still true

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.

czterystaczwarty avatar May 28 '20 14:05 czterystaczwarty

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?

rikutiira avatar Jun 08 '20 10:06 rikutiira

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.

erikras avatar Jun 08 '20 15:06 erikras

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.

jeremy8883 avatar Jun 24 '20 07:06 jeremy8883

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.

jeremy8883 avatar Jun 25 '20 05:06 jeremy8883

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.

benwiles1 avatar May 03 '21 16:05 benwiles1

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.

JavierMNieto avatar Dec 29 '21 04:12 JavierMNieto

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.😞

sathishkumar294 avatar Jan 26 '22 17:01 sathishkumar294

same here, any workarounds?

AndriyHromyak avatar May 11 '22 14:05 AndriyHromyak

I am stuck in this too...

oribenez avatar Oct 09 '22 23:10 oribenez

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...

Dozalex avatar Feb 24 '23 17:02 Dozalex

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 🙏

younessbennaj avatar Aug 31 '23 12:08 younessbennaj

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?

georgiosd avatar Jan 26 '24 11:01 georgiosd