Working with different input types

Since RVF validates and submits data directly from the html form element by default, nearly anything you can do with plain html forms can be done with RVF. All native input types are supported out of the box.

When you're using RVF to observe or set the value of a field, it should hopefully "just work" the way you expect. But for completeness, this page is going to cover all the different input types and how they interact with RVF.


Common traits

Types

RVF exports helper types for the values of a few different input types. For they most part, they're what you expect, but it's worth noting that empty inputs are generally represented as null in caes where the main type of the input isn't a string.

Setting default values

All input types (except for file) can have their default value set using a string. For input types that represent some other type (like number), you can usually set the default value using that type.

Observing / setting values

The type of the value you get when you call form.value(fieldName) is always the same as the type you pass into defaultValues. You should use the same type when calling form.setValue(fieldName, value).

Validating

Unless you're using state mode, the data received by your validator will always be a string (except for file inputs) OR a string[].

  • If only one input is in the form for a given field, the value will be a string.
  • If multiple inputs in the form have the same name, the value will be a string[].

If you are using state mode, then the value passed to your validator will be the same value that you would get out of form.value("myField").


Number inputs

Relevant types

  • NumberInputValue
    • number | null

Setting default values

Number inputs can be set using either a number, a string, or null.

const form = useForm({
  defaultValues: {
    age: "25",
    numberOfPeople: 10,
  },
})

const age = form.value("age");
//    ^? string
const numberOfPeople = form.value("numberOfPeople");
//    ^? number

Observing / setting values

  • An empty number input will always have a value of null.
  • If you set the default value using a string, the value will always be a string (or null).
  • If you set the default value using a number or null, the value will always be a number (or null).

Validating

Like most input types, the value inside the native form element is always a string. Unless you're using state mode, your validator should be able to handle that.

  • If you're using zod, you can use z.coerce.number to handle this.
  • If you're using yup, then yup.number() already handles this.

If you're using state mode, then the value passed to your validator will be the same value that you would get out of form.value("myField").


Checkboxes & Checkbox groups

Checkboxes are pretty versatile. They can be used as a simple boolean flag, or as a group of checkboxes represented by an array of strings. And this is all without any extra work or controlled components.

Usage

Call getInputProps for each checkbox input and pass in a type of "checkbox" and the value of the checkbox.

<label>
  Checkbox with a value
  <input
    {...form.getInputProps("myCheckbox1", {
      type: "checkbox",
      value: "checkbox-value"
    })}
  />
</label>

<label>
  Boolean checkbox
  <input
    {...form.getInputProps("regularCheckbox", {
      type: "checkbox",
    })}
  />
</label>

Setting default values

Checkboxes can be set using either a boolean or a string[] as the default value. If you use a string[], the checkbox will be checked if the checkbox's value prop is in the array.

Observing / setting values

RVF will keep the value returned from form.value("myCheckbox") consistent with the type you provided as the default value. If you don't set a default value, then it will return a boolean.

Validating

Single checkboxes

Checkboxes commonly trip people up. The way checkboxes are represented in FormData on form submission is like this:

  • A checked checkbox will be in the FormData as the value of its value prop.
  • If there is no value prop, then the checkbox will be in the FormData as "on".
  • An unchecked checkbox will not be in the FormData at all.

Even if you used a boolean as the default value, you're validator should expect to receive "on" | undefined.

  // This will be "on" or undefined
  <input type="checkbox" name="myCheckbox" />

  // This will be "hello" or undefined
  <input
    type="checkbox"
    name="checkboxWithValue"
    value="hello"
  />

Checkbox groups

Checkbox group add an extra layer of complication here, because the FormData doesn't tell us how many checkboxes there are, just which ones are checked. RVF handles that like this:

  • If only one checkbox is checked, the value your validator receives is a string.
  • If multiple checkboxes are checked, then the value is a string[].

This means that your validator should expect to receive undefined | string | string[]. If you're using zod, you can handle this case easily using repeatable from zod-form-data.

  // The value of this group could be
  // - A single string ("value1")
  // - An array of strings (["value1", "value2"])
  // - undefined

  <input type="checkbox" name="group" value="value1" />
  <input type="checkbox" name="group" value="value2" />
  <input type="checkbox" name="group" value="value3" />

Radio groups

These are much simpler than checkboxes.

Usage

Call getInputProps for each radio input and pass in a type of "radio" and the value of the radio.

<label>
  Value 1
  <input
    {...form.getInputProps("myRadio", {
      type: "radio",
      value: "value1"
    })}
  />
</label>

<label>
  Value 2
  <input
    {...form.getInputProps("myRadio", {
      type: "radio",
      value: "value2"
    })}
  />
</label>

Setting default values

You can only set the default value using a string.

Observing / setting values

The value returned will always be a string, unless no radio is checked at all, in which case it will be undefined.

Validating

Like when observing the values, the value will always be a string. The exception is when no radio is checked at all, in which case it will be undefined.


Selects

Usage

Select elements work naturally with getInputProps with no other considerations.

// single select
<select {...form.getInputProps("mySelect")}>
  <option value="foo">Foo</option>
  <option value="bar">Bar</option>
  <option value="baz">Baz</option>
</select>

// multi select
<select {...form.getInputProps("mySelect", { multiple: true })}>
  <option value="foo">Foo</option>
  <option value="bar">Bar</option>
  <option value="baz">Baz</option>
</select>

Setting default values

Single selects can be set using a string and multi selects can be set using an array of string.

Observing / setting values

Just like with default values, single selects will always be a string and multi selects will always be an array of string.

Validating

This one has a gotcha. When the FormData is submitted, it doesn't include any information about whether or not the select is a multi-select. Therefore, RVF works around this like this:

  • If one option is selected, the value will be a string.
  • If multiple options are selected, the value will be an string[].

If you're using zod, you can handle this case easily using repeatable from zod-form-data.


File inputs

File inputs actually pretty inflexible. Try running this code to see what happens. If you do that, you might see an error like this:

This input element accepts a filename, which may only be programmatically set to the empty string.

const FileInputTest = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  return <input type="file" defaultValue="myFile.txt" />;
}

Unfortunately, this means we can't set the default value of a file input or modify it with setValue, unless that value is an empty string ("").

Relevant types

  • SingleFileInputValue
    • null | File
  • MultiFileInputValue
    • null | File[]

Setting default values

It isn't possible to set the default value of a file input.

Using setValue

You can use setValue if and only if the value you pass in is an empty string ("") or null. This will clear the file input.

Observing the value

Oberving a file input value works.

  • If the input is set to multiple, then the value will have the type null | File[].
  • If the input is not set to multiple, then the value will have the type null | File.

Other types

We've covered the most common input types, but there are many more types of inputs out there. In all other cases, RVF will treat the value as a string.