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
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 completeFormState
, 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 decodeFormData
again. Ideally this method would returnvalues
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 thetransform
docs are in the vanilla js library?mergeForm
withuseFormState
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 😊