houseform / houseform

Simple to use React forms, where your validation and UI code live together in harmony.
https://houseform.dev
MIT License
640 stars 30 forks source link

[Feature] Form Arrays #4

Closed crutchcorn closed 1 year ago

crutchcorn commented 1 year ago

Currently, with the introduction of nested field support, we're able to support array-style fields something like the following:

<Field name="test[0].name">
    // ...
</Field>
<Field name="test[1].name">
    // ...
</Field>

This should work, but there is a problem with this; when submitting a form, it will not treat test as an array but rather as an object:

{test: {0: {name: "val"}, 1: {name: "val"}}}

It would be nice to add a better story for field arrays in HouseForm.

Further, it would also be nice to have the ability to validate these field arrays using Zod's z.array() type to provide validation for these values.

Potential APIs

There are multiple different APIs we could use to achieve this effect.

Option 1: Reusing <Field>s

<FieldArray name="people" onChangeValidate={z.array(z.any()).min(3, "You must have three items in your array"}>
  {({ fields, setFields }) => (
    <>
      {fields.map((field, index) => (
        <Field key={field.key} name={`people.${index}.username`}>
          {({ value, errors, setValue }) => (
            <div>
              <input
                value={value}
                onChange={e => setValue(e.target.value)}
                placeholder="Username"
              />
              {errors.map(error => <span key={error}>{error}</span>}
            </div>
          )}
        </Field>
      ))}
      <button onClick={() => setFields([...fields, { name: "" }])}></button>
    </>
  )}
</FieldArray>

Pros:

Cons:

Option 2: Use dedicated components

<FieldArray name="people" onChangeValidate={z.array(z.any()).min(3, "You must have three items in your array"}>
  {({errors, fields, setFields}) => (
    <>
      {fields.map((_, index) => (
        <ArrayField key={fields.key} name={`people.${index}.username`} onChangeValidate={z.string()}>
          {({value, setValue, errors}) => (
            <div>
              <input
                value={value}
                onChange={e => setValue(e.target.value)}
                placeholder="Username"
              />
               {errors.map(error => <span key={error}>{error}</span>}
            </div>
          )}
        </ArrayField>
      ))}
      <button onClick={() => setFields([...fields, { name: "" }])}></button>
    </>
  )}
</FieldArray>

Notes:

Pros:

Cons:

Option 3: Use dedicated components with helper functions

<FieldArray name="people" onChangeValidate={z.array(z.any()).min(3, "You must have three items in your array"}>
  {({fields, add, remove, insert, move}) => (
    <>
      {fields.map((_, index) => (
        <ArrayField key={index} name={`people.${index}.username`} onChangeValidate={z.string()}>
          {({value, setValue, errors}) => (
            <div>
              <input
                value={value}
                onChange={e => setValue(e.target.value)}
                placeholder="Username"
              />
               {errors.map(error => <span key={error}>{error}</span>}
            </div>
          )}
        </ArrayField>
      ))}
      <button onClick={() => add({ name: "" })}>Add</button>
      <button onClick={() => remove(-1)}>Remove last</button>
      <button onClick={() => insert(3, {name: ""})}>Insert at third</button>
      <button onClick={() => move({from: 0, to: 1})}>Move the first array item to the second place</button>
    </>
  )}
</FieldArray>

This is the winner

Other Implementations

crutchcorn commented 1 year ago

Work is being done on this branch:

feat/field-array