TanStack / form

🤖 Powerful and type-safe form state management for the web. TS/JS, React Form, Solid Form, Lit Form and Vue Form.

Home Page:https://tanstack.com/form

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mismatch in array handling between useForm and FormData

probablykabari opened this issue · comments

Describe the bug

In the docs, everywhere that array fields are mentioned the format is different than what works on the server. Take this example:

<form.Field key={i} name={`people[${i}].name`}>
  {(subField) => (
    <input
      value={subField.state.value}
      onChange={(e) =>
        subField.handleChange(e.target.value)
      }
    />
  )}
</form.Field>

In FormData this will become { "people[0].name": "value" } and will not validate because the decode-formdata library expects the format to be { "people.0.name": "value } or { "people.[0].name": "value } and thus the value is empty.

See line

const data = decode(formData, info) as never as TFormData

Note: Currently a workaround is simply to not use <input name={subField.name} ... /> on the input and instead do <input name={'people.${i}.name'} ... /> . So this may be resolved by highlighting this in the React docs.

Your minimal, reproducible example

Not sure how to run tests on those TBH

Steps to reproduce

Example test suite provided

Expected behavior

type FormSchema = {
  people: { name: string }[]
}
const factory = createFormFactory<FormSchema>({
  defaultValues: {
    people: []
  },
  onServerValidate({ value }) {
    if (!value.people) return `People is required but is ${typeof value.people}`
    if (value.people.length < 1) return "Not enough people to party"
  }
})

async function action(prev: unknown, data: FormData) {
  return await factory.validateFormData(data)
}

test("array validation for people[0].name", async ({ expect }) => {
  const formData = new FormData()

  formData.set("people[0].name", "Joe")
  formData.set("people[1].name", "Jane")

  expect(await action(null, formData)).toEqual({
    errorMap: {
      onServer: undefined
    },
    errors: []
  })
})
test("array validation for people.0.name", async ({ expect }) => {
  const formData = new FormData()

  formData.set("people.0.name", "Joe")
  formData.set("people.1.name", "Jane")

  expect(await action(null, formData)).toEqual({
    errorMap: {
      onServer: undefined
    },
    errors: []
  })
})

test("array validation for people.[0].name", async ({ expect }) => {
  const formData = new FormData()

  formData.set("people.[0].name", "Joe")
  formData.set("people.[1].name", "Jane")

  expect(await action(null, formData)).toEqual({
    errorMap: {
      onServer: undefined
    },
    errors: []
  })
})

Result:

test:    × array validation for people[0].name
test:    ✓ array validation for people.0.name
test:    ✓ array validation for people.[0].name

test:  FAIL  __tests__/array.ts > array validation for people[0].name
test: AssertionError: expected { …(2) } to deeply equal { …(2) }
test: 
test: - Expected
test: + Received
test: 
test:   Object {
test:     "errorMap": Object {
test: -     "onServer": undefined,
test: +     "onServer": "People is required but is undefined",
test:     },
test: -   "errors": Array [],
test: +   "errors": Array [
test: +     "People is required but is undefined",
test: +   ],
test:   }

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

Vitest

TanStack Form adapter

react-form

TanStack Form version

0.19.0

TypeScript version

5.3.2

Additional context

No response

This is where our docs could be improved. Unfortunately, due to the complexity of FormData parsing, we're handing off that "everything is a string" parsing to decode-formdata: https://github.com/fabian-hiller/decode-formdata

Where you need to pass in the schema you expect the FormData to parse to on the server. Closing as a can't fix, but do feel free to make a PR to docs to clarify this for us if you're comfortable doing so :)

@crutchcorn Mind if I make a bigger PR to address the following, all related to using server validation. Thus far my usage in practice has been less than welcoming.

  • validateFormData doesn't ever return a complete FormState, only errors. This creates a bit of an annoyance because you use this function to handle errors, but if you want to use the data for anything (especially in TS) you have to decode FormData again. Ideally this method would return values after your validator has already done this for you. Also the docs should reflect this.
  • useTransform seems to break if the response value of an action is null. Not a big deal but unexpected and this hook and it's use are a mystery. This is sometimes an issue (in NextJS) when redirecting from a server action. I'm guessing the transform docs are in the vanilla js library?
  • mergeForm with useFormState with a default state deeply merges with the default. So, if there is an array value in the default it incorrectly will merge server values with default values rather than replacing them on initial load.

@probablykabari thanks for reporting these. Glad to see someones validating our SSR stuff in prod.

Both of these sound like bugs, not docs issues. The first should be easy to solve and the second might be a slight addon to mergeForm or something.

PRs are always welcome 😊