BearStudio/formiz

Access Form Values with Field's "name" String

ACampbell12 opened this issue ยท 14 comments

Is your feature request related to a problem? Please describe.

Hello! I would like to pick your brain about some potential behavior. I've been using Formiz for a little bit now and love the implementation of nested fields. That being said, it would be awesome if I could extract a field from the form state using the same nested string that I used to create it.

Example:

// Field creation
<FormizWrappedField name="nested.field" defaultValue="Hello!" />

// Form structure
{
  nested: {
    field: "Hello!"
  }
}

// Access Field
const form = useForm();
const valueByObject = form.values.nested.field // Returns field value
const valueByString = form.values["nested.field"] // Always returns undefined

Is there a design choice as to why this is not possible? I figure that this behavior would also be consistent with the behavior of setFieldsValues because setFieldsValues can only set fields using nested strings and not objects, for instance:

form.setFieldsValues({"nested.field": "New Value!"}) // This will set the field's value
form.setFieldsValues({ nested: { field: "New Value!" }} ) // This will not

See the below Code Sandbox for a functional example.

Describe the solution you'd like
I'm not sure. I haven't looked through the source significantly as I wanted to get your thoughts on this first.

The only reason that I could see this not working would be if there was a non-nested field with a "." in it's name, BUT if that is true, then it should be a nested field anyway, right?

Describe alternatives you've considered
None as of yet! Happy to discuss them though!

Additional context
Code Sandbox

This library rocks!

@ACampbell12 Thanks a lot for the feedback :)
You read my mind ^^
I plan to add a flatValue object for accessing values without the nested format :)

<FormizWrappedField name="nested.field" defaultValue="Hello!" />
<FormizWrappedField name="collection[0].field" defaultValue="World!" />

// form.values
{
  nested: {
    field: 'Hello!'
  },
  collection: [
    { field: 'World!' },
  ],
}

// [NEW] form.flatValues
{
  'nested.field': 'Hello!',
  'collection[0].field': 'World!',
}

Tell me if it can solve your problem :)

I think it's not so much work, I will try to implement it this week if I have some available time :)

@ACampbell12 It was pretty easy to add :)
So new version 1.0.0-rc.18 of @formiz/core is available with the flatValues option ๐ŸŽ‰

Documentation https://formiz-react.com/docs/core/use-form#flatvalues

Amazing! That is perfect!

Quick question: Does this affect subscriptions? If I do

<FormizWrappedField name="nested.field" defaultValue="Hello!" />
<FormizWrappedField name="collection[0].field" defaultValue="World!" />

const form = useForm({ subscribe: { form: true, field: ["nested.field"] }})

Will form.values and form.flatValues only update when "nested.field" updates?

Thanks again for all your work!

Edit:

I had some time and was able to play with it. Looks like the subscriptions are all good! My question about setFieldsValues still stands though. Do you think it would be a good idea to be able to do both of the following?

// This currently works
form.setFieldsValues({ 
  'nested.field': 'Salutations!', 
  'collection[0].field': 'Earth!' 
})

// This currently does not
form.setFieldsValues({ 
  'nested: {
    'field': 'Salutations!'
  },
  'collection': [ {
    'field': 'Earth!' 
  } ]
})

Also, I submitted a quick pull request to add flatValues to useFormValues for TypeScript support (#39)

@ACampbell12 Yes I agree, but instead of allowing nested format in if the setFieldsValues and the invalidateFields.
We should create 2 new functions.
As I don't want breaking change we can't rename setFieldsValues to setFieldsValuesFlat for example.
But we can create a setFieldsValuesNested and a invalidateFieldsNested maybe.

I'm open to other naming or other ideas :)

Also the main issue is to deal with object values (or array values).

Imagine a field nested.field with the following value:

{ url: '...', id: 1 }

So the global nested object will be:

// values
{ 
  nested: {
    field: { url: '...', id: 1 },
  }
}

// flatValues
{ 
  'nested.field': { url: '...', id: 1 },
}

How you can know that the field is nested.field and not nested.field.url and nested.field.id?

By the way I've the idea to provide a defaultValues on the <Formiz> component to give an object of default values for the form.
When a field mount, he will look into this object to set the defaultValue (if no defaultValue is provided to the field).

How you can know that the field is nested.field and not nested.field.url and nested.field.id?

I don't want breaking change we can't rename setFieldsValues to setFieldsValuesFlat for example.

Totally agree on both of these. Hmm well then maybe we don't need the ability to use setFieldsValues with a nested object.

The use case that I was considering was reinflating forms. For example, someone fills out the form partially, the draft is saved in the database as some JSON object matching a partially-filled version of your form schema, then, on continuation, you could just

form.setFields( partialObject );

and the form state is returned. That being said, this also can create problems because conditional fields will not be set. This would happen if they are not mounted when you initially call setFields, which, if they rely on another field's value to conditionally mount, they likely won't be.

I think your idea of a defaultValues object on the actual <Formiz> component solves those issues. If you set

<Formiz defaultValues={partialObject}>
...

Then conditional fields would be set because they would grab their value when they mount. This would also provide the ability to "reset" the form to the loaded partial state.

This is a familiar pattern for people who have used Formik.

Yes and based on Formik, I think initialValues is a better name. Because it's not default state but initial state :)

<Formiz initialValues={partialObject}>
...

Sorry, I've been traveling and will be for a week or two. When I get back, I'll start actually reading through your source and getting a feel for the library. I definitely think the initialValues feature would be great but I'm going to close this issue as the original issue has been resolved with the addition of flatValues.

Feel free to reopen if you'd rather work the initialValues from here! Thanks!

@ACampbell12 The new version 1.0.0-rc.22 of @formiz/core is available with a new option: initialValues ๐ŸŽ‰
See the documentation: https://formiz-react.com/docs/core/formiz#initialvalues

Awesome! I recently came back to my project that used Formiz and also saw some Ttypescript errors which you also fixed. Great job!

Hello @ACampbell12, I finally found a way to support nested values in setFieldsValues ๐Ÿ˜…
The 1.2.0 version of @formiz/core is available with this feature ๐Ÿค—
https://github.com/ivan-dalmet/formiz/releases/tag/v1.2.0

setFieldsValues({
  fieldName: 'New value',
  nested: {
    subField: 'New value',
  },
})

Better late than never ๐Ÿ˜„
Have a great weekend

As always, you're awesome! Thanks for letting me know!