lunar icon indicating copy to clipboard operation
lunar copied to clipboard

[Suggestion] Remove setting `value: ''` for all input fields

Open ktmud opened this issue 5 years ago • 7 comments

Not sure if this is intentional, but I see here partitionFieldProps sets value = '' for all form fields every time they render:

https://github.com/airbnb/lunar/blob/e37800438b2007ba1ac3821b7edf7713c2970856/packages/core/src/components/FormField/partitionFieldProps.ts#L68-L71

This basically makes an input unusable without a state bind to input.value. Even the most basic input example in the Storybook does not work (users cannot type anything).

It's generally a bad practice to write what users typed right back into an input. It triggers an unnecessary re-render, increases the risk of circular callbacks and greatly increases code complexity. For example, a PR in Bighead had to introduced a new state just to use a debounced onChange handler.

Code as simple as

export default function DebouncedInput(props: DebouncedInputProps) {
  const { onChange, debounceTime = 150, ...restProps } = props;
  const handleChange = debounce(onChange, debounceTime);
  return <Input {...restProps} onChange={handleChange} />;
};

had to change to

export default function DebouncedInput(props: DebouncedInputProps) {
  const { onChange, debounceTime = 150, ...restProps } = props;
  const [value, setValue] = useState(restProps.value);
  const handleChange = debounce(onChange, debounceTime);

  return <Input {...restProps} value={value} onChange={(
    newValue: string,
    event: React.ChangeEvent<HTMLInputElement>
  ) => { 
    setValue(newValue);
    handleChange(newValue, event);
  }} />;
}

And most importantly, manually setting user input as users type has the side effect of interfering with other native browser behaviors, breaking things like autocomplete and foreign language input (I've seen such bugs many many times).

In case a clean input is desired, developers should provide value="" themselves.


The fix is as simple as remove value: ''. If this change would break current developer expectations or other features, it's the best to at least provide an option to disable this behavior.

ktmud avatar Mar 10 '20 08:03 ktmud

It's generally a bad practice to write what users typed right back into an input.

Do you mean controlled components are bad in general?

Normally, in the Bighead example, the parent component DeployModal would pass it's own state value search down to the Input field. In this case, because it's Debounced, we want

  1. Debounced state value in DeployModal
  2. Instant state value in Input

But that seems like a very specific case right? Outside of debouncing, it's hard to think of examples where an uncontrolled component would be preferable. I'm sure they exist, but this seems like an exception, and not a general rule.

schillerk avatar Mar 10 '20 18:03 schillerk

Do you mean controlled components are bad in general?

I actually wasn't aware of controlled components. Thanks for calling it out. My comments mostly came from my experience pre-React era. There were just so many nasty bugs I have dealt with that originated from setting input values as users type.

I agree controlled components can be helpful for most cases, but IMO developers should still have the option to not use it. For simpler form fields, there might not be even a need to use states. Setting value='' took that option away.

ktmud avatar Mar 11 '20 00:03 ktmud

Btw, sorry if I sounded more forceful than I actually was. This just reminded me of some very awful bugs.

ktmud avatar Mar 11 '20 01:03 ktmud

Haha, no problem.

At least for DX, our UIs almost always adapt to user input in some way, as opposed to having uncontrolled input that we only read on submit. For example:

  • Live search/filtering
  • Autocomplete
  • Field validation

Aside from debouncing, I really struggle to think of a good use case.

FWIW, other component libraries do something similar: https://github.com/ant-design/ant-design/blob/master/components/input/Input.tsx

One downside is that not having a default value allows users to change between a Controlled and Uncontrolled component. I.e. Value starts out undefined, but is later set by the parent component, resulting in: Warning: Input is changing an uncontrolled input of type text 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

Since React generates an error anyway, I'm not super concerned about this. I don't have a strong opinion either way.

schillerk avatar Mar 11 '20 15:03 schillerk

For debouncing, it's rarely done in handlers. Debouncing is done on API calls, and those are usually throttled instead, outside of the presentational component. It's app logic. I disagree with removing it. The form inputs from the form package handle default values, setting the values, and the flexibility to change them with callbacks like onBatchChange.

stefhatcher avatar Mar 11 '20 17:03 stefhatcher

@schillerk Ant design does not reset value to '' every time the component rerenders. See https://codesandbox.io/s/antd-input-vs-lunar-input-oo7tn

IMO this value always resets to '' behavior would be a surprise to most developers. setState should not change where the state is not being used.

If we really want to enforce controlled components and keep this reset, maybe make value a required prop so future developers can feel less surprised.

ktmud avatar Mar 11 '20 18:03 ktmud

All form components in core are controlled components, and this is the pattern in which controlled works. Resetting value to an empty string isn't necessarily correct, as the controlled components require the consumer to pass a value in to work. The empty string is just the default state.

Removing that empty stirng is a breaking change, and we just released v3, so this wont happen anytime soon. However, we can probably control this through a prop (like uncontrolled), coupled with partitionFieldProps.

milesj avatar Mar 11 '20 18:03 milesj