Open crobinson42 opened 5 years ago
Up
up
up
@erikras, any thoughts here?
Currently, my implementation is a bit hacky.
I created a HOC that I wrap my Field
components and on componentDidMount
triggers the fields input.onChange
handler if there is a prop initialValue
passed from the Field
.
@wmertens has also long requested this. I'd accept a PR implementing this. ð
The idea is to immediately call change
on mount, like @crobinson42's hack? Or some deeper mechanism to actually set the initial
value?
The case where I need this is in a stepper/wizard form where fields are added dynamically depending on previous form values. Because of this, there isn't a way (IMO) to set the Form
's initialValue
(or reset). I guess one question is, does calling input.onChange
have other side-effects, ie: meta.touched
changes when input.onChange
is called?
No, change
only changes the value. Without a deeper change to FF, it will make it dirty
, though.
You know, this is a decent case in favor of https://github.com/final-form/final-form/pull/199. ðĪ
One more thing: there are use cases for both initial and default values. Maybe the naming isn't right, but consider these scenarios:
IMHO, in 1, the form should be dirty and fields should show errors, in 2 the form shouldn't be dirty but the field can already show errors, and in 3 the form shouldn't be dirty and fields shouldn't show errors.
Maybe we can also add per-field flags dirty
and touched
?
Published a solution in react-final-form@4.1.0
and final-form@4.12.0
. Let me know what you guys think?
@erikras it's my understanding and expectation that defaultValue
on a <Field />
should only be set if there are no form initialValues set.
The current behavior is that I'm passing initial values to a Form
component and a date Field
inside the form has defaultValue={new Date()}
which is overwriting the field's value when I'm editing a record in the form.
Thoughts on Field
only setting the defaultValue
prop if there is not already an initialValues
field set?
Difficult semantics: the date should be set if no initial value is given, but it shouldn't make the form dirty.
Perhaps you should set it as initialValue and let it be overwritten by the form iV if that value is present. However, I would expect field iV to overwrite form iV ðĪ
@crobinson42 @wmertens @erikras I think the new defaultValue
and initialValue
fields are handy, but a third option would be really useful that is essentially the same as initialValue
but lets the initialValue from Form
override it.
I'm not sure what the right name would be here, but I think something like this would work:
const FieldWithDefault = ({ unsetInitialValue, ...props }) => {
const { initialValues } = useFormState({ initialValues: true });
const initialValue = prop.name in initialValues
? initialValues[prop.name]
: unsetInitialValue;
return <Field initialValue={initialValue} {...props} />;
}
@redbmk I don't agree with this approach:
a third option would be really useful that is essentially the same as initialValue but lets the initialValue from Form override it.
My reasoning is by lookin at a hierarchy view of a form with who has priority - the closest data provided to a component/input-field should win.
Yeah, @redbmk, can you make a case for why/when this third overridable field initial value would be useful?
@crobinson42 I actually thought I was saying the same thing as you
@erikras it's my understanding and expectation that
defaultValue
on a<Field />
should only be set if there are no form initialValues set.The current behavior is that I'm passing initial values to a
Form
component and a dateField
inside the form hasdefaultValue={new Date()}
which is overwriting the field's value when I'm editing a record in the form.Thoughts on
Field
only setting thedefaultValue
prop if there is not already aninitialValues
field set?
Here's a simplified example of the scenario I'm facing:
const initialValues = existingDataFromServer || {}; // empty object for new entries
// form's initialValues
<Form initialValues={data} {...etc} />
// some fields
<label>
<Field type="checkbox" name="showOnDetailReport" unsetInitialValue={true} />
Show on detailed report
</label>
<label>
<Field type="checkbox" name="showOnSummaryReport" />
Show on summary report
</label>
My thinking is if you're editing an existing record from the server, I would want to use the data that was already stored. But if you're creating a new record, it should start out with the value given in the field. I used unsetInitialValue
here because defaultValue
and initialValue
are already taken and I can't really think of a better name ðĪ·ââ.
Maybe the right way to do this is when setting the form's initial values:
const initialValues = {
showOnDetailReport: true,
...existingDataFromServer || {},
};
It just seemed a little more intuitive to have the field itself declare what its default should be. If that's too much of an edge case or anti-pattern I'm happy to use a workaround.
@redbmk I think there's a little ambiguity in the comments here ðĪŠ
Here's my understanding...
defaultValue
used if there is no initial value for a form/field
initialValue
populate form/field (takes precedence over defaultValue
)
<Form
initialValues={{ name: 'cory' }}
render={() => (
<Field
defaultValue="default" // only used if Form does not have initialValues for this field
name="name"
type="text"
/>
)}
/>
<Form
initialValues={{ name: 'cory' }}
render={() => (
<Field
initialValue="jack" // overrides Form's initialValues for this field
name="name"
type="text"
/>
)}
/>
@crobinson42, hmm... I agree with that concept, and that was what I would have expected defaultValue
to do intuitively, but it doesn't actually work that way. According to the docs (and this example I hacked together from another example), defaultValue
will override initialValue
and mark the field as dirty. From the final-form docs:
defaultValue?: any
â ïļ You probably want
initialValue
! â ïļThe value of the field upon creation. This value is only needed if you want your field be
dirty
upon creation (i.e. for its value to be different from its initial value).
I guess what I'm suggesting is either a breaking change for defaultValue
, or a third option that behaves the way you describe defaultValue
behaving.
I believe defaultValue should be the DEFAULT. That means, if NO initialValue was passed in, the defaultValue would be used. If there is an initialValue, it should override the defaultValue. One work around is using an OR in initialValues and skip using defaultValue all together like so:
const initialValues = { name: item.name || defaultName }
The only problem with this if you need the field to be dirty like defaultValue sets it, you wont get it with this method.
@erikras should we open another issue for further discussion? Sounds like I'm not the only one that would expect defaultValue
to work a little differently than it currently does.
I think the normal usage would/should be like inline(defaultValue) and header(initialValue) where in the defaultValue will be the value even if there is an initialValue. Landed on this issue because I have initialValues for a set of fields in a FieldArray but my defaultValues for plain Fields displays the defaultValues but goes blank after. I have a bit complex and big form where in I have to use different child components with other Fields in it. Needed to separate them in child components to have separate concerns and limit codes on the parent form. Though I moved the Field Arrays with the parent form as I can't set initialValues for the array in the child component.
I have to agree, I also expected the functionality to be exactly what @crobinson42 and @redbmk expected.
So logic would be something like this:
Check if initialValue
in <Field />
exists?
yes
==> use that value
no
==> check if initialValues
exists in <Form />
yes
==> use that value
no
==> use defaultValue
in <Field />
Currently the defaultValue
will override both initialValue
and initialValues
https://codesandbox.io/s/react-final-form-simple-example-yx7bh
Agree with @valstu on this too. Wondering if it makes sense to either support it or have a prop for it? Or make defaultValue
not override but initialValue
to
also agree with @valstu , isn't this how it was in redux form?
Running into the same challenge with react-final-form-arrays
where I'd like to have a default 1 item. That works but once in editing and reusing the same fields, the initialValue
overrides the passed in initialValues
:(.
@erikras I am happy to put up a PR for this. We can either:
initialValue
override Form's initialValues
but defaultValue
to notinitialValues
. Maybe the prop is a true/false or enum of overrideInitialValues={'initialValue' | 'defaultValue' | null}
My current workaround:
import get from 'lodash/get';
import { useFormState } from 'react-final-form';
...
const { initialValues } = useFormState({ subscription: { initialValues: true } });
const field = useField('name', { initialValue: get(initialValues, 'name') });
@erikras I created a PR to go in line with what was mentioned above.
@crobinson42 @redbmk @valstu @mmahalwy @paulhan221
@broveloper Thanks for digging into this and getting the ball rolling! I have a question about the implementation though:
The value of the field upon creation only if both
initialValue
isundefined
and the value frominitialValues
is alsoundefined
. The field will bedirty
whendefaultValue
is used.
I would expect defaultValue
not to make the field dirty
. This is already a breaking change, so if we want to adjust that behavior now would be a good time. I can see cases where either behavior would be desirable though. Maybe there could be another configuration option to adjust the behavior would be a good option here?
@wmertens suggested:
Maybe we can also add per-field flags
dirty
andtouched
?
I'm not sure if this is what he meant, but it might look something like this:
<Form initialValues={{ qwer: 123, foo: "baz" }} onSubmit={console.log}>
{(handleSubmit) => (
<form onSubmit={handleSubmit}>
<Field name="asdf" defaultValue={123} />
<Field name="zxcv" defaultValue={456} dirty />
<Field name="qwer" initialValue={789} touched />
<Field name="foo" defaultValue="bar" />
</form>
)}
</Form>
In this case, if initialValues
didn't include any of these fields, then:
asdf
would be set to 123
but wouldn't be dirty or touchedzxcv
would be set to 456
and would be dirty but not touchedqwer
would be set to 789
and would be marked as touched (e.g. in order to show errors)foo
would be set to "baz"
and wouldn't be dirty or touched@redbmk indeed, this is least surprising API, it looks great
@redbmk thanks, applied your suggestions
Also, i didn't want to complicate this issue with the debate over if it should be dirty
or not. I feel like that could be opened up as a separate discussion since it could be argued both ways.
I simply wanted to get the ball rolling on having defaultValue
function in a way most users desired.
bump
Are you submitting a bug report or a feature request?
feature request
What is the current behavior?
A
<Form>
is required to be providedinitialValues
to set default values for fields.What is the expected behavior?
<Field>
can accept a propinitialValue
ordefaultValue
that is registered with the parent<Form>
's initial values.This would be oober convenient in many cases where fields are dynamically generated or conditionally rendered in the
<Form>
.I'm happy to work up a PR if you're accept/review the effort @erikras