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.

Repeated field names

There are some situations where you might want to have multiple inputs with the same name. A common use-case for this would be groups of checkboxes. The way this is handled in remix-validated-form, is that any fields that are submitted with the same name will be grouped together as an array. If only one field is submitted for a given name, then it will be treated as a single value.

Example:

const inputs = (
  <>
    <input
      type="checkbox"
      name="myCheckboxGroup"
      value="value1"
    />
    <input
      type="checkbox"
      name="myCheckboxGroup"
      value="value2"
    />
  </>
);

// If both are checked
const result = {
  myCheckboxGroup: ["value1", "value2"],
};

// If only one is checked
const result = {
  myCheckboxGroup: "value1",
};

Validating

When constructing your validation schema, you need to keep in mind that the value could be an array or a single value. The easiest way to handle this is with repeatable from zod-form-data.

import { zfd } from "zod-form-data";

export const validator = withZod(
  z.object({ likes: zfd.repeatable() }),
);

Considerations for your components

One thing to keep in mind when using repeated field names, is that all of the inputs will receive the same error prop. For the most common use-cases (checkbox groups and radio groups), you probably only need to display the error once for the whole group. If you do need to display an error for each input, you're probably better off naming your inputs with the array syntax (e.g. myField[0]).

Here's one possible implementation of this:

export type CheckboxProps = {
  label: string;
  name: string;
  value?: string;
};

export const Checkbox: FC<CheckboxProps> = ({
  label,
  name,
  value,
}) => {
  const { getInputProps } = useField(name);
  return (
    <div>
      <label>
        {label}
        <input
          {...getInputProps({ type: "checkbox", value })}
        />
      </label>
    </div>
  );
};

export type CheckboxGroupProps = {
  label: string;
  name: string;
};

export const CheckboxGroup: FC<CheckboxGroupProps> = ({
  label,
  name,
  children,
}) => {
  const { error } = useField(name);
  return (
    <fieldset>
      <legend>{label}</legend>
      {children}
      {error && <p className="myErrorClass">{error}</p>}
    </fieldset>
  );
};

Full example

Which of these do you like?