Open lydell opened 5 years ago
+1
Strange this hasn't received a response of any kind considering this is a breaking change according to the documentation of allowNull
.
By default, if your value is null,
<Field/>
will convert it to '', to ensure controlled inputs.
Potentially related to the discrepancies in formatOnBlur
behavior described by #560
This is definitely a regression introduced in the recent refactor, as I have another project running 4.x and this is not an issue.
Okay, after a bit of digging, this is a bit more subtle than I originally thought. The behavior of allowNull
hasn't changed. In both 4.x and newer versions, it uses strict equality. I was thinking it was a change from ==
to ===
so it would catch undefined values before but no longer does. Instead the change is even more confusing:
4.1.0
// Field.js
if (formatOnBlur) {
value = Field.defaultProps.format(value, name)
} else if (format) {
value = format(value, name)
}
Current
// useField.js
if (formatOnBlur) {
if (component === 'input') {
value = defaultFormat(value, name)
}
} else {
value = format(value, name)
}
That's weird. So previously, you have to opt out of format
if you don't want it to run (b/c it's defined in defaultProps
. And with formatOnBlur
, it always uses the default format in render (but the passed in format on blur). That's a wow. And now, you can't opt out of format
(probably fine, though).
Now, it only runs the default format if you pass component="input"
, which I verified will remove the error. However, this leads to even more potential confusion, b/c of how Field
orders its render checks:
if (typeof children === 'function') {
return (children: Function)({ ...field, ...rest })
}
if (typeof component === 'string') {
// ignore meta, combine input with any other props
return React.createElement(component, { ...field.input, children, ...rest })
}
return renderComponent(
{ ...field, children, component, ...rest },
`Field(${name})`
)
So, if you pass a function as child to <Field component="input" format={format} formatOnBlur>
, you get the full behavior, and you won't get an uncontrolled error. But if you pass a render prop, you'll get errors b/c as you can see it just does createElement
and spreads the props down (which will get spread onto the input
, resulting in errors).
All of this is a bit confusing. Best case for me is allowNull
uses ==
to catch undefined
as well. IMO, there's usually not a useful distinction between undefined
and null
anyways, and I take a cue from Elm and use a Maybe
type for anything where these distinctions aren't meaningful (e.g. type Maybe<T> = T | null | undefined;
).
In the meantime, there's a few things we can do. Pass initialValue
to Field
, pass initialValues
to Form
, create a helper that normalizes input.value
, etc.
Hopefully this can be meaningfully addressed, b/c it leaves the API in a strange place as is.
Update edit: This is even worse than I described. While initialValue
will fix focus and blur errors, there are still errors thrown if you type something then delete the character, which makes the input value go from ""
to undefined
. The only way to fix this is to catch it in the input itself: <input {...input} value={input.value || ''}/>
. Not lovely.
Same issue Controlled/Uncontrolled warning here, the whole code in useField is super confuse
// useField.js
if (formatOnBlur) {
if (component === 'input') {
value = defaultFormat(value, name)
}
} else {
value = format(value, name)
}
component === input
only?defaultFormat
and not the format
from the param?@csantos1113 Regarding 1., my assumption is just that using component="[TagName]"
was not really intended to be overloaded by passing children or a render function. It's basically for the most simple case where you don't need access to the {input, meta}
or your elements at all. The fact that it "fixes" the error is likely just an unintended consequence of the rendering order (as I described) and the fact that "input"
type is normalized differently than other types.
There is branching logic for normalizing other component types starting at line 212 of the latest tag (v6.3.0). It's a bit convoluted, but normalization is done by some combination of input.type
and component
type.
I do think this logic could be simplified greatly, however, but it would require a deeper abstraction. For now, the issue w/ formatOnBlur
should be addressed at least.
This behavior seems broken. It makes it impossible (unless you're willing to tolerate the controlled/uncontrolled warning from React) to use {...input}
to spread field props into an underlying <input>
element. Instead, you need to do this:
<input type="text" {...input} value={input.value || ''} />
I'm also confused why the code that @csantos1113 excerpted above. Why use defaultFormat
instead of format
?
This issue is causing problems for me when resetting a form containing an input element with formatOnBlur enabled:
<Field format={formatPhoneNumber} formatOnBlur {...props}>
{(fieldProps: FieldRenderProps<string>) =>
(
<React.Fragment>
<label>{label}</label>
<input
type="text"
{...input}
value={input.value || ''}
/>
</React.Fragment>
);
}
</Field>
When I don't add {input.value || ''}, the input value will not be emptied.
Are you submitting a bug report or a feature request?
Bug report. (I think!)
What is the current behavior?
I use
format
andformatOnBlur
to trim whitespace from a field when the user leaves it. I know that I must not returnundefined
informat
.I get this warning when typing in the field:
What is the expected behavior?
No warnings!
Note that if I remove
formatOnBlur
the warnings do not appear.Sandbox Link
https://codesandbox.io/embed/nervous-williams-k1ldk
What's your environment?
react-final-form: 6.1.0 final-form: 4.14.1 browser: Tried Chrome and Firefox
Other information
The code from the sandbox for convenience:
Also, thank you for this fantastic form solution! :100: