1

In React, I'm trying to create a form in which some values are optional. I have the following TypeScript interface:

export interface DataModel {
    title:      string,
    count?:     number,
}

count is optional, but if it's set, it has to be a number.

I've set up my initial object like this:

const INITIAL_VALUE = {
  title: "",
  count: undefined
}

const dataItem = useState(INITIAL_VALUE);

Then I create a form:

<form onSubmit = { handleSave }>
  <input
    name     = "title"
    value    = { dataItem.title}
    onChange = { handleChange }
    type     = "text"
    placeholder = "Enter title"
  />
  <input
    name        = "count"
    value       = { dataItem.count}
    onChange    = { handleChange }
    type        = "number"
  />
</form>

So the count input is initially getting a value of undefined, but then when I enter a value, I get the error:

A component is changing an uncontrolled input to be controlled

I can avoid this by initializing it with a value, but as it's optional I don't want to do that. How should I handle this?

Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
Sharon
  • 3,471
  • 13
  • 60
  • 93

2 Answers2

2

When you define the value property of a component to be undefined, it essentially means that the component is an uncontrolled component. However if in the next cycle you have a value property, the behaviour of the component changes from unControlled to controlled.

To solve this, you can add a default value for your inputs

<form onSubmit = { handleSave }>
  <input
    name     = "title"
    value    = { dataItem.title || ''}
    onChange = { handleChange }
    type     = "text"
    placeholder = "Enter title"
  />
  <input
    name        = "count"
    value       = { dataItem.count || 0}
    onChange    = { handleChange }
    type        = "number"
  />
</form>
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
1

You should provide a proper initial value. And, that could be 0 or empty string - "". Empty string should be fine even when input type is number. Because e.target.value is always string.

If you want to store it as a number, turn the value to number using parseInt, parseFloat or Number (check details to make sure they fit the need) in your event handler or do this before making payload for the API call.

Hence, you should simply define the interface and initial values as:

export interface DataModel {
  title: string;
  count: number | ""; // count is a number or empty string
}

const INITIAL_VALUE = {
  title: "",
  count: "",
};

This way the component always stays as a controlled component.

Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
  • One can also use [`e.target.valueAsNumber`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement). Note that its value is `NaN` when you enter something like `123XYZ` (similar to `Number("123XYZ")`). – Ajeet Shah Apr 10 '21 at 12:57
  • 1
    Sorry, I've been working on other things this week and haven't had a chance to get back to this until today! – Sharon Apr 15 '21 at 15:44