4

I want to store the state of my React Final Form somewhere. Right now that is in a React Hook. I'm currently using useMethods, similar to useState.

The form I currently have is part of a multi-step process, and when I navigate to another page which the form is not on, it is unmounted (of course). When re-mounted, I want to provide the form with initial values (available here).

The problem is that when my state changes the form re-renders and I always end up in an endless loop, because:

  • The FormSpy stores changes in the form
  • The state is changed
  • Form is re-rendered
  • The FormSpy stores changes in the form
  • And so on...

I also have a problem that when initial values are provided, the field state is not populated (dirty, touched, etc.), so the form looks strange when re-mounted because some of the labels are tied to the meta fields. It seems like we need some kind of way to store all of the form state, not only the values as the component which contains the Form can unmount and re-mount n number of times.

What I want:

  • Changes to immediately to be stored in my state
  • Re-renders somehow remember (I can store this in my own state if needed) the touched, dirty, etc. state

My starting point was to look at Auto-save with Debounce (although I don't need to debounce, since I'm storing it locally).

What is the best way to handle this?

Anders Stensaas
  • 749
  • 5
  • 18
  • Sounds more like you need the Wizard Form example, which keeps partial form state outside of the form. No? https://github.com/final-form/react-final-form#wizard-form – Erik R. Jul 23 '19 at 16:15
  • I guess that makes sense. I'm storing the state in the top-most `App` component. The form itself can be unmounted and re-mounted, but I guess what I need is a class component which stores the initial values in its own state on `componentDidMount`, much like the Wizard Form example. Would that be the correct approach, @ErikR.? – Anders Stensaas Jul 23 '19 at 17:29
  • And I don't think this will store the values with metadata on them. How would one go forward and store everything in my own state? Since I need to provide initial values for the touched and active states as well. – Anders Stensaas Jul 23 '19 at 17:45
  • I tried this. The values are stored like I want, but the touched, active etc. states are not persisted, and there's no way to provide these initial values to the form as I know of. Either I can't unmount the form, or I would need some kind of method to persist the whole form state somewhere and provide it in the initial values when the component mounts. – Anders Stensaas Jul 24 '19 at 12:43

2 Answers2

2

Always fascinated to discover a use case I'd never have considered. Since react-final-form@6.1.0, you can now provide your own final-form form instance, via the form prop. This, if stored at a higher place in the tree – with a useRef(), probably – might let you maintain form state for longer?

Just a guess. Do report back if it works...

Erik R.
  • 7,152
  • 1
  • 29
  • 39
  • Thanks for the answer, Erik. Didn't know about this. Seems like I've missed that in the docs. I'll try storing what I can, and try passing the form instance myself and see what happens. – Anders Stensaas Jul 24 '19 at 18:17
  • I tried this out right now. Both with providing a `form` prop to the `Form` component and used a `ref` on the `form` tag. That didn't help much. I still got the same issue where all the `meta` and `error` information is no longer present. I don't think this is solvable now, as there's no way to provide initial values for the form state, only for the values for the inputs themselves. I really like to use this library, it's so easy to use: validation, error messages and use the metadata. In this case, it seems like we need to change to use something else or somehow don't unmount the form at all. – Anders Stensaas Jul 25 '19 at 07:32
  • If you could put together a minimal sandbox, I'd love to continue to work to solve your issue... – Erik R. Jul 25 '19 at 22:09
  • Yes, I'll create a sandbox for you which illustrates the problem. Just give me some time. – Anders Stensaas Aug 06 '19 at 11:47
  • I guess this illustrates the problem: https://codesandbox.io/embed/react-final-form-initial-values-example-i969u. I don't know why the `touched` isn't set before blurring two times. Remember to do that before re-mounting the component. As you will see, the touched values aren't maintained when the Form unmounts (obviously). You can try adding a ref etc. if you want to see if it helps. It didn't help me at all. We've solved this (hacky) by looking for existing values (given initialValues) and render the error messages etc. based on that, but it would be nice to store formstate somewhere. – Anders Stensaas Aug 06 '19 at 13:18
0

I just had a similar issue, and manged to create a decorator that remembers the touched (or any other field state) and then re applies that state when the field is mounted again. One major issue is that this causes a flicker as the field is rendered not touched on mount, then the decorator is notified, then the field is rendered touched.

Some example code (not tested):

function decorator(form) {
  const storedTouched = {};
  return form.subscribe(({touched}) => {
    for(const field of Object.keys(touched)) {
      if(touched[field]) {
        storedTouched[field] = true;
      }
    }
    for(const field of Object.keys(storedTouched)){
      if(touched[field] === false) { // check against false since unregistered fields are undefined
        form.mutators.setFieldTouched(field, true);
      }
    }
  }, {touched: true});
}

You could also expose the storedTouched to allow setting any field touched, not just those that are already mounted/registered. (eg initialize it to the fields that were already touched last time)

Blue Cheetah
  • 3
  • 1
  • 1