form icon indicating copy to clipboard operation
form copied to clipboard

Get all fields / touched fields

Open bkniffler opened this issue 7 years ago • 28 comments

Hey, could we expose a function that returns all fields and/or all touched fields? Or maybe an option to validateFields { touchedOnly: true } to only return those that were touched?

Would be pretty cool to only save actually changed fields through a patch method to the server instead of all values, even those that didn't change.

bkniffler avatar May 19 '17 00:05 bkniffler

i think you can use isFieldsTouched and getFieldsValue to do this.

paranoidjk avatar May 19 '17 01:05 paranoidjk

Could you please elaborate? isFieldsTouched returns true/false if something changed and getFieldsValue returns field values.

How can I get all the fields that changed? This should be really common in case that you don't want to have classic PUT/POST, but PATCH methods that will only send the changed form data.

Imagine a form with tabs and 30 fields. If only the "name" field changed and the save button is clicked, I'd like to run a method that will return ["name"] or have a possibility to implement that without needing to attach onChange on every single form item.

bkniffler avatar May 31 '17 09:05 bkniffler

isFieldsTouched can receive params,like a array of field names. please read api doc carefully

paranoidjk avatar May 31 '17 09:05 paranoidjk

Okay, since I'm not sure if I'm making myself clear, here an example: https://www.webpackbin.com/bins/-KlSiBx4TBghlGriZJF9

Just change the field "name" to something like "Name1" and click save. It works, but only due to

const transform = (obj, arr = [], parent = '') => {
  if (!obj) return [];
  if (parent) parent = `${parent}.`;
  Object.keys(obj).forEach(key => {
    const fullKey = `${parent}${key}`;
    if (obj[key] && typeof obj[key] === 'object') {
      transform(obj[key], arr, fullKey);
    } else {
      arr.push(fullKey);
    }
  });
  return arr;
};
const onClick = (form) => () => {
  const value = form.getFieldsValue();
  // Get an array of changed fields
  const arr = transform(form.getFieldsValue()).filter(x => form.isFieldTouched(x));
  // Get the final object with only changed keys
  const final = form.getFieldsValue(arr);
  console.log(final);
};
  1. Is there a better way to achieve what I'm trying to do? 1 b. If not: Wouldn't it more sense to add "getChangedFields" to return an array of the changed fields and/or "getChangedFieldValues" that returns an object with only the changes applied (like my above example) to the form? This shouldn't be too hard since that stuff is already present in the form state, isn't it?

bkniffler avatar May 31 '17 09:05 bkniffler

Sorry, i was wrong,isFieldsTouched can not achieve your requirements. isFieldsTouched(names: String[]): Bool: Whether one of the inputs' values had been change.

But you can do it by use isFieldTouched to loop all your fileds.

cc @benjycui

paranoidjk avatar May 31 '17 10:05 paranoidjk

Yes, thats what I'm doing right now, but it would be better if we had an easy to use function instead of what I'm doing in the example above :)

I suggest

  • getTouchedFields(): [String] => Returns array of changed field-keys like ['user.name', 'comment']
  • getTouchedValues(): Object => Returns object with changes like { user: { name: 'XY' }, comment: 'xy' }

bkniffler avatar May 31 '17 10:05 bkniffler

I also ran into this issue just now actually, I want to know if any of the values for any of the input fields within the form has changed or not. Is touched will be true as soon as there is some interaction with the input field, but that doesn't mean that the field is "dirty". Basically there is no easy way to know if one(or more/all) of your fields are dirty. This is useful when you want to enable a submit button only if the form is "dirty".

The suggested getChangedFields() method by @bkniffler would suffice for my use case. Or having an explicit form.isDirty() => boolean method which calls into getChangedFields would be even better :)

BTW this is pretty common requirement for a piece of UI when using forms. While you could do all kinds of deep equality checks to accomplish this, it feels pretty dirty. Moreover I assume that there is a map in rc-form which is already keeping track of this stuff.

agonbina avatar May 31 '17 14:05 agonbina

isFieldsTouched and isFieldTouched is for https://ant.design/components/form/#components-form-demo-horizontal-login

If you want to know which fields are changed, you can diff final fields and initialValue.

benjycui avatar Jun 01 '17 02:06 benjycui

Diffing isn't nearly as good a solution as exposing the functions. You are already storing the dirty state of each field, why not expose the methods then? I really don't understand why we should redundantly diff the data. I also see a few problems with diffing (using multiple sources for initialValue, having a ton of fields that need to be diffed, diffing nested fields / deep-diffing).

Please consider exposing the functions, I suppose its only a few lines of code necessary.

Also, stating that isFieldsTouched/isFieldTouched is only for one specific use-case is strange. Why do you store the dirty state per field then instead of one dirty state for the whole form, if its only to disable the save button?

bkniffler avatar Jun 01 '17 06:06 bkniffler

@benjycui keep in mind that there is also a performance hit if you rely on diffing(ex on every render). Moreover doing deep equality checks of JS maps is tricky and can result in inconsistent outcomes.

agonbina avatar Jun 01 '17 08:06 agonbina

Actually, rc-form can not get all the dirty fields easily now. Because the data structure of nested fields. So, rc-form need to traverse the whole tree to find out dirty fields..

benjycui avatar Jun 01 '17 08:06 benjycui

Maybe later, but I am not going to implement this feature recently.

benjycui avatar Jun 01 '17 08:06 benjycui

I'm also having a bit of frustration with this.

Why aren't we saving a global form dirty or pristine (a la Redux Form)

Having this isFieldsTouched is nice but if a user changes a field but then reverts it, is still counts as "touched" even though the form is actually "pristine" (or not dirty).

This should be a built in feature no?

jwmann avatar Oct 17 '18 21:10 jwmann

I'm also facing the same issue.

Do we have any update on this ?

AshuDeshpande avatar Apr 01 '19 12:04 AshuDeshpande

In my case, I'm using isFieldsTouched to detect if any form field has been changed. That seems to work fine. The problem is that, when I click on a "save" submit button, I expect the function isFieldsTouched to return false (since all the form items have been submitted). But I'm always receiving true on that function, no matter if we submit the form or not. How is isFieldsTouched supposed to return false after a form is submitted ?

Maikel-Nait avatar May 27 '19 05:05 Maikel-Nait

This is also pertinent to my use case where I want to prevent user input loss by wrapping the antd Form with my own definition that tracks if any of the fields are dirty. It would be useful to be able to retrieve references to the fields from the form without having to know the names, or to get a general status if any of the fields are dirty.

ahouck avatar Jul 22 '19 20:07 ahouck

As @jwmann I'm longing the same pristine feature. Any news on this?

wiltfm avatar Aug 16 '19 12:08 wiltfm

@wilkmoura I had opted to totally give up on using the included rc-form in favour of using Formik combined with Formik-antd that I personally worked on.

It works quite well and I actually prefer it over rc-form now.

jwmann avatar Aug 22 '19 16:08 jwmann

thanks for the feedback @jwmann, definitely gonna look into it.

wiltfm avatar Aug 22 '19 16:08 wiltfm

In my case, I'm using isFieldsTouched to detect if any form field has been changed. That seems to work fine. The problem is that, when I click on a "save" submit button, I expect the function isFieldsTouched to return false (since all the form items have been submitted). But I'm always receiving true on that function, no matter if we submit the form or not. How is isFieldsTouched supposed to return false after a form is submitted ?

Any updates? Did you find any solutions?

vixeven avatar Nov 19 '19 07:11 vixeven

I'm looking to simply disable the submit button until the form has values and is validated. I'm looking for something like form.validateFields(), but only returning data, checking untouched fields, and not showing errors.

mikeaustin avatar Apr 14 '20 18:04 mikeaustin

I created an issue pertaining to this very thing plus more... Ant Design - issue #29316 ... also, I don't really see any reason why most form functionality couldn't be ported ... it would make for a more flexible end product and promote clean code.

I have also always wanted to see a feature to disable all of the fields in a form, this is typical functionality for forms ... especially when you provide avalidateFields promise, which returns the values on resolve ... it would be nice to do a form.DisableAll(); while waiting for validation to complete.

Honestly, I could probably throw together a complete issue summarizing all of this stuff on the rc-form repo, linking here and other places. I would be open to personally working on the features if someone could give me a little help.

Like some others mentioned, rc-form has fallen behind on some nice features like this...

spencer741 avatar Feb 13 '21 09:02 spencer741

+1 isDirty fn

menosprezzi avatar Feb 22 '21 22:02 menosprezzi

Faced same issue - need to validate only touched fields. My workaround is:

const touchedFields = Object.keys(form.getFieldsValue()).filter((el) => form.isFieldTouched(el));
form.validateFields(touchedFields)

error404as avatar Jan 17 '22 14:01 error404as

+2 isDirty fn

doraemonxxx avatar May 18 '22 17:05 doraemonxxx

@error404as It works if it's a simple form, but for more hierarchical forms I'm using what @bkniffler suggested a few years back :) Though I have modified it a little to support Form.List as well

import { FormInstance } from 'antd';

const transform = (obj: any, arr: any[][] = [], parent: any[] = []) => {
  if (!obj) return [];
  Object.keys(obj).forEach((key) => {
    const fullKey = parent.concat(key);
    if (obj[key] && typeof obj[key] === 'object') {
      transform(obj[key], arr, fullKey);
    } else {
      arr.push(fullKey);
    }
  });
  return arr;
};

export const useTouchedFields = (form) => () => {
  // Get an array of changed fields
  const arr = transform(form.getFieldsValue()).filter((x) => {
    return form.isFieldTouched(x);
  });
  // Get the final object with only changed keys
  const final = form.getFieldsValue(arr);
  return final;
};

rmagon avatar Sep 12 '22 15:09 rmagon

There's still no getTouchedFields and/or dirty flag in 2023? Cmon... this is so basic.

clmz avatar Mar 17 '23 07:03 clmz

Yep there is no way in rc-form to figure out if values really changed compared to initialValues. And on top onValuesChange and onFieldsChange is not triggered when calling setFieldValue or setFieldsValue which makes it even harder.

What we need is a global form state that exposes touched, dirty, pristine etc. like any other form library is doing it.

chillyistkult avatar Mar 17 '23 15:03 chillyistkult