question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Controlled/Uncontrolled warning when using formatOnBlur

See original GitHub issue

Are you submitting a bug report or a feature request?

Bug report. (I think!)

What is the current behavior?

I use format and formatOnBlur to trim whitespace from a field when the user leaves it. I know that I must not return undefined in format.

I get this warning when typing in the field:

Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

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:

<Form
  onSubmit={values => {
    console.log("SUBMIT", values);
  }}
>
  {({ handleSubmit }) => (
    <form onSubmit={handleSubmit}>
      <Field name="name" format={(value = "") => value.trim()} formatOnBlur>
        {({ input }) => {
          return <input {...input} />;
        }}
      </Field>
    </form>
  )}
</Form>;

Also, thank you for this fantastic form solution! 💯

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:12
  • Comments:7

github_iconTop GitHub Comments

7reactions
justingrantcommented, Nov 1, 2019

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 ?

5reactions
bfrickacommented, Aug 9, 2019

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Initializing React number input control with blank value?
<MyInput> is changing an uncontrolled input of type number to be controlled. Input elements should not switch from uncontrolled to controlled ( ...
Read more >
How to Create a React Form: Controlled vs. Uncontrolled ...
In this article, we'll cover two ways to create React forms: the HTML way, with uncontrolled components, and the best practice, with controlled...
Read more >
a component is changing a controlled input to be uncontrolled ...
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined...
Read more >
Controller | React Hook Form - Simple React forms validation
This simplifies integrating with external controlled components with non-standard prop names. Provides onChange , onBlur , name , ref and value to the...
Read more >
A component is changing an uncontrolled input to be controlled
The warning : A component is changing an uncontrolled input to be controlled . This is likely caused by the value changing from...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found