Remix Validated Form

RVF v6 has been released!

For new projects, we recommend starting with v6. For existing projects, there's a migration guide available.

<T>(field: string, formId?: string) => [T, (value: T) => void]

Used for tracking the value of a field in state. The return signature is the same as the React's own useState, but this hook does a few things for you automatically.

Caveats:

The companion hook useUpdateControlledField can also be used to update the value being tracked with this hook.

Controlled Components

This hook can make it easy to integrate controlled components. Here's an example using a made-up ControlledSelect component.

import {
  useField,
  useControlField,
} from "remix-validated-form";
import { ControlledSelect } from 'some-library';

type MySelectProps = {
  name: string;
  label: string;
  options: string[];
};

export const MySelect = ({
  name,
  label,
  options
}: MySelectProps) => {
  const { error, getInputProps, validate } = useField(name);
  const [value, setValue] = useControlField<string[]>(name);
  return (
    <div>
      {/*
        Even though we're tracking the value as an array,
        we can still serialize the data
        using repeated inputs if we want.
      */}
      {value.map(val => (
        <input
          type="hidden"
          name={name}
          key={val}
          value={val}
        />
      ))}
      <ControlledSelect
        label={label}
        options={options}
        selectedOptions={value}
        onSelectionChange={(newSelection) => {
          setValue(newSelection);
          validate();
        }}
      />
      {error && (
        <span className="my-error-class">{error}</span>
      )}
    </div>
  );

Globally tracking / updating all fields

If you regularly have advanced use-cases that require updating field values, you may opt to track field state in all fields. This is as simple as adding useControlField to all your input components.

Note: This would prevent you from using repeated fields. In this case, you would favor using array syntax instead.

Here's a modified version of MyInput from the integrate your components page.

import {
  useField,
  useControlField,
} from "remix-validated-form";

type MyInputProps = {
  name: string;
  label: string;
};

export const MyInput = ({ name, label }: MyInputProps) => {
  const { error, getInputProps } = useField(name);
  const [value, setValue] = useControlField(name);
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input
        {...getInputProps({
          id: name,
          value,
          onChange: (e) => setValue(e.target.value),
        })}
      />
      {error && (
        <span className="my-error-class">{error}</span>
      )}
    </div>
  );
};

Occasional field tracking

If you only sometimes need to update the values of a field, this can be used on a case-to-case basis in your form.

export const MyForm = () => {
  const [value, setValue] = useControlField(
    "myField",
    "myForm",
  );

  useEffect(() => {
    // Now we can update the value of the field
    // for whatever reason we need.
    setValue("Some value");
  });

  return (
    <ValidatedForm validator={myValidator} id="myForm">
      <MyInput
        name="myField"
        // This assumes your input component
        // has a `value` and `onChange` prop
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </ValidatedForm>
  );
};