field-form icon indicating copy to clipboard operation
field-form copied to clipboard

useForm does not return same FormInstance

Open shllg opened this issue 3 years ago • 1 comments

  • Tested on 1.25.2 up to 1.27.0
  • Tested on React 17 and 18
  • ViteJS 2.9+

Scenario

I'm having a modal. When opening it, we're fetching data and populating it into a form (form.setFields).

Pseudo:

const [form] = useForm();

// ...

useEffect(() => {
  // having data here for sure...
  form.setFields(data...)
}, [...])

// ...

return (
...
<Form form={form}>
 ....
</Form>
...
)

Actual Behavior

  • Once the modal is loaded, the form is not populated. After double checking everything, we saw that form.setFields(data...) has been called with the right data.
  • Pressing save on any related file in development (component refresh / rerender) will then populate the form correctly (without reloading the data from the server)

Expected Behavior

Populating the form immediately displays the information.

Analysis

We tried to figure out what the issue could be. To debug, we changed the code of useForm and Form to include a random number into the FormInstance. When logging the that data, we're getting the following:

log: useForm - has no "current" 
log: useForm - build FormStore/FormInstance with random number

// Render of our component
log: ourComp - FORM: 0.8044077487840133

// Render of our component
log: ourComp - FORM: 0.9797179135137914
// ^ why do we get a new FormInstance here?

log: ourComp - populate data
// After this, every render displays `0.9797179135137914` although `0.8044077487840133` was the random of the first render

// Now refreshing one component by code refresh

// Render of our component
log: ourComp - FORM: 0.9797179135137914

// Render of our component
log: ourComp - FORM: 0.9797179135137914
log: ourComp - populate data

// Everything is displayed correctly, "0.8044077487840133" is gone.

Important to notice is, that useForm internally just calls once:

var formStore = new FormStore(forceReRender, Math.random());
formRef.current = formStore.getForm();

which is correct.

Now the question is, where the first instance is coming from? I think this is the part preventing a correct render in the first place. Our code populates the second instance. When forcing the component to rerender, it'll take the new (correct) FormInstance and everything will be fine afterwards.

I hope somebody got an idea. I'll continue to dig deeper.

shllg avatar Jun 24 '22 16:06 shllg

After investigating further I think I found the issue: useForm returns the current value of a useRefobject. To notify components for changes regarding the form, there is the forceUpdate state setter:

function useForm<Values = any>(form?: FormInstance<Values>): [FormInstance<Values>] {
  const formRef = React.useRef<FormInstance>();
  const [, forceUpdate] = React.useState({});

  // ....
      const forceReRender = () => {
        forceUpdate({});
      };
  // ....

  return [formRef.current];
}

Now when looking into the usage of that forceReRender function inside the FormStore, we can see the issue:

private notifyObservers = (
    prevStore: Store,
    namePathList: InternalNamePath[] | null,
    info: NotifyInfo,
  ) => {
    if (this.subscribable) {
      // .... notifications for useWatch etc.
    } else {
      this.forceRootUpdate();
    }
  };

Once there is a subscribable registered (e.g. by using useWatch deeper down the components tree), it'll never trigger an update for the form again. With that, there is no information about new values that are related to the getForm method. As a result, code like that will not be triggered again once the form is ready:

  useEffect(() => {
    // ... form related code
    form.setFields(fieldData)
  }, [form, fieldData])

A workaround is to do something like that:

const [form] = useForm<FormValues>()
useWatch<string>('...anyField...', form)

useEffect(() => {
  // ... form related code
  form.setFields(fieldData)
}, [form, fieldData])

Any suggestions how to solve this without a workaround?

shllg avatar Jun 27 '22 12:06 shllg