final-form / react-final-form

🏁 High performance subscription-based form state management for React
https://final-form.org/react
MIT License
7.39k stars 481 forks source link

resetFieldState breaking hard on not-yet mounted fields #621

Open csantos1113 opened 5 years ago

csantos1113 commented 5 years ago

Are you submitting a bug report or a feature request?

bug report

What is the current behavior?

Getting the following error when conditionally rendering and resetting a field (subcategory) based on another field (category)

Uncaught TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at getValidators (final-form.es.js:568)
    at runFieldLevelValidation (final-form.es.js:581)

What is the expected behavior?

// Resets the subcategory to undefined
form.change("subcategory", undefined);
// Resets the field state
form.resetFieldState("subcategory");

to work whether or not the field subcategory is mounted or not.

Sandbox Link

https://codesandbox.io/s/react-final-form-simple-example-7jt63

What's your environment?

react 16.9.0 final-form 4.18.5 react-final-form 6.3.0

Other information

The expectations:

Juansasa commented 5 years ago

Any progress on this ? The issue happened when we programatically resetting the field using hooks regardless if the field is mounted or not

cristiammercado commented 4 years ago

Any advance? Please, a possible solution to this problem is necessary :( .

UltimateForm commented 4 years ago

I am having this issue when trying to reset field too

(using react-admin)

vitalylukyanenko commented 4 years ago

resetFieldState still doesn't work

santino commented 4 years ago

+1 resetFieldState not working for me neither on a conditional field.

My use case is that when a "parent" field value changes I want to reset the state of the depending conditional fields like they had never been interacted with. In this case the conditional field might have never been or no longer be rendered; but it has been registered and so I would expect Final Form to imperatively perform operations on it.

This issue has been opened since Sept would be great to get a comment from maintainers so we know what we can expect.

sntxerror commented 4 years ago

It seems like something similar happens with FieldArrays

squalvj commented 3 years ago

i solved this by putting the resetStateField inside the setTimeout ...form.change("subcategory", undefined); setTimeout(() => {form.resetFieldState("subcategory")})

santino commented 3 years ago

Unfortunately this still doesn't work for me. It will cause error TypeError: Cannot convert undefined or null to object.

With some debugging the issue seems to be similar to what I described almost 1 year ago. Final Form seems to unregister the field after performing a resetFieldState on a conditional field.

Some debugging reveals that runFieldLevelValidation function is invoked with an incomplete field object:

{
  error: undefined,
  field: {
    active: false,
    lastFieldState: undefined,
    modified: false,
    touched: false,
    valid: true,
    validating: false,
    visited: false
  }
  ...
}

As opposed to a full field object that would look something like this:

{
  error: undefined,
  field: {
    active: false,
    data: {},
    lastFieldState: {
      active: false,
      data: {},
      dirty: false,
      dirtySinceLastSubmit: false,
      error: '',
      initial: '',
      invalid: true,
      modified: false,
      modifiedSinceLastSubmit: false,
      name: 'name',
      pristine: true,
      submitFailed: false,
      submitSucceeded: false,
      submitting: false,
      touched: false,
      valid: false,
      value: '',
      visited: false,
      validating: false
    },
    modified: false,
    modifiedSinceLastSubmit: false,
    name: 'name',
    touched: false,
    valid: true,
    validators: {},
    validating: false,
    visited: false
  }
  ...
}

This issue has been opened over 1.5 years ago; is quite disappointing that it didn't receive any attention from maintainers. The whole final form project didn't receive much love for a long time actually 😞

squalvj commented 3 years ago

Unfortunately this still doesn't work for me. It will cause error TypeError: Cannot convert undefined or null to object.

With some debugging the issue seems to be similar to what I described almost 1 year ago. Final Form seems to unregister the field after performing a resetFieldState on a conditional field.

Some debugging reveals that runFieldLevelValidation function is invoked with an incomplete field object:

{
  error: undefined,
  field: {
    active: false,
    lastFieldState: undefined,
    modified: false,
    touched: false,
    valid: true,
    validating: false,
    visited: false
  }
  ...
}

As opposed to a full field object that would look something like this:

{
  error: undefined,
  field: {
    active: false,
    data: {},
    lastFieldState: {
      active: false,
      data: {},
      dirty: false,
      dirtySinceLastSubmit: false,
      error: '',
      initial: '',
      invalid: true,
      modified: false,
      modifiedSinceLastSubmit: false,
      name: 'name',
      pristine: true,
      submitFailed: false,
      submitSucceeded: false,
      submitting: false,
      touched: false,
      valid: false,
      value: '',
      visited: false,
      validating: false
    },
    modified: false,
    modifiedSinceLastSubmit: false,
    name: 'name',
    touched: false,
    valid: true,
    validators: {},
    validating: false,
    visited: false
  }
  ...
}

This issue has been opened over 1.5 years ago; is quite disappointing that it didn't receive any attention from maintainers. The whole final form project didn't receive much love for a long time actually 😞

have you try wrap the resetFieldState inside the setTimeout ? and make sure all the field is rendered before you resetFieldState

santino commented 3 years ago

Yes, I did try wrapping resetFieldState in a setTimeout; otherwise, I wouldn't have commented after your suggestion πŸ˜‰

First of all, I was dispatching all my form operations inside a form.batch() given this is more performant as any potential listener for updates is informed only after you have performed all operations; as opposed to every time a single operation completes (hence potentially causing multiple re-renders). Inside batch the setTimeout didn't make any difference at all, even introducing a delay; most likely because this still batches all operations under the hood.

Even if someone is willing to compromise on the performance downside of removing batch, the setTimeout does not really solve this problem. It helps only if your conditional fields are always going to be visible. In this case, it does reset their state; annoyingly not their value, so you still need to have two separate form operations to first imperatively change the field value and then reset the field state.

Most likely people are using conditional fields precisely because their sub/child-fields are not always going to be visible on the screen under a condition that depends on the value of a parent/master-field.

I suggest everyone to test the setTimeout workaround properly and you will notice that when you have a condition that hides your conditional field and then another condition that should re-display it; your application will crash with the error mentioned above: TypeError: Cannot convert undefined or null to object.

squalvj commented 3 years ago

Yes correct, the setTimeout is only work when particular field is going to be visible for sure

And i think setTimeout worked because it runs after the component refresh the state into the new one and render that "conditional Field"

So far on my case, no crash issue to use the setTimeout because the component sure that particular field will rendered and get the state reset, ill let you guys know if this hacky way is breaking in the future.

iaremarkus commented 1 year ago

I'm not sure the setTimeout is fixing the cause issue here. The attached screenshot is from a button onClick. The form fields definitely exist.

As the button is clicked, I am logging form, and form is correctly logged.

But after 3 seconds when I try use resetFieldState, its saying form is null / not found? 🧐

pic-002677

Rahul-Sagore commented 4 months ago

I also faced this error, in my case, field might not get rendered based on some condition, so directly calling resetFieldState('middle_name') was throwing error if that field is not rendered.

So I used getFieldState method to check if field metadata is present or not. This will return undefined if field is not rendered:

if (getFieldState('middle_name')) {
  resetFieldState('middle_name')
}

console.log('first_name', getFieldState('first_name'))
console.log('middle_name', getFieldState('middle_name'))
Screenshot 2024-06-27 at 12 19 42

Alternatively

Can also use getRegisteredFields method, this will return array of field names that are registered, so if a field did not render due to some condition, then it wont be present in this list:

console.log(form.getRegisteredFields())
// ['first_name', 'last_name']