sveltekit-superforms
sveltekit-superforms copied to clipboard
Send nested data in a plain form
I would like to understand why forms with nested data require JS in order to work.
I thought one of the ideas of superforms was to make use of the progressive enhancement that sveltekit provides, i.e. allowing forms to work without JS if necessary. And I know that nested data works with forms in general. So why is JS required for superforms to allow nested data?
Nested data doesn't work with forms in general, since html forms only deals with string values in non-nestable input elements. So a serialization is required for nested data (with the exception of arrays of primitive values).
// You can't express this structure in a plain html form.
{
tags: [ { id: 1, name: 'first' }, { id: 2, name: 'second' }]
}
...with the exception of arrays of primitive types like string[]
or number[]
because with those, you can reuse the name attribute.
<input type="text" name="tag" id="tag-01" />
<input type="text" name="tag" id="tag-02" />
<input type="text" name="tag" id="tag-03" />
const formData = await event.request.formData()
formData.get('tag') // gets the first tag in the dom (string)
formData.getAll('tag') // gets all tags in the dom (string[])
Yes, this is automatically handled by Superforms, so you don't need to go through FornData: https://superforms.rocks/concepts/nested-data#arrays-with-primitive-values
Thank you for your answers! Would it be possible to somehow flatten before creating the form and unflatten again after validation in the action handler? Or is this kind of pattern not supported by zod? For example if you started with your example of
{
tags: [ { id: 1, name: 'first' }, { id: 2, name: 'second' }]
}
this be flattened to something html forms would support like:
{
"tags.0.id": 1,
"tags.0.name": "first",
"tags.1.id: 2,
"tags.1.name": "second"
}
I just don't know how one could define a zod schema for this flattened version in order to create the superform, or if it is even possible.
It's not a Zod thing, it only matches keys of the object to the schema. So it has to be a different dataType
, nested-html
for example. And the keys should be compatible with the FormPath type, so they should be in the format tags[0].id
.
With that, it's not that hard to implement, but I haven't planned any feature releases yet, so this issue can be used as an enhancement, and I would be happy to see a PR for it!
Okay, that sounds promising, thank you. I haven't looked into the codebase yet, but if you say it shouldn't be hard to implement then maybe I will try take a look this week. If you had any pointers off the top of your head to help me get started then that would be most appreciated - otherwise I'll give a shout if anything is unclear once I've taken an initial look.
Sure, it's a server-side thing, so in superValidate.ts there is a formDataToValidation function that is the best entry point for this.
I'm not sure though how to detect that it's this nested-html
datatype that's been posted, especially since it's plain html (assume no JS), so the only way to communicate things to the server is to add another field or as a query parameter.
+1 for this issue. Nested fields is a great feature but it needing JS kinda defeats SvelteKit's "Let's make Apps that work without JS, let's use forms instead of fetch because they work without JS". So right now I'm doing it via functions nestify and flatify that convert to and from this dotted notation (although maybe better and more complex notations exist for such cases?) and I have to use them in every superForms in-out kind of "boundary"
I find this "no JS"-idea to be wishful thinking in most cases. For just browsing a content site and maybe searching with a one-field form, fine, that should work. But for any kind of complexity, and especially if you're submitting nested data, a website that works without one line of JS (while still supporting the other case with a nice UX) is very ambitious and time-consuming.
Btw @douglasward there is a function called splitPath that you may also find useful, when building the nested object from the posted string paths.
And finally, there are some "test.ts" files in src
, if you can make another one with tests for this feature, that'd be great. :)
This could be useful for this issue: https://svelte.dev/repl/d8916d45012241dab5962c1323604fe9?version=4.2.0
This is a great idea!
When the source for v2 is ready for public scrutiny, it'll be much easier to add this feature.
A challenge of working with nested data in plain forms is having a system to coerce the proper data types when parsing the formData
entries. One approach is to have field-level preprocessing in your schema, like zod-form-data does it. But this means using primitive schemas that aren't your validator's natively provided ones and it also means you end up with composed schemas that are only geared towards formData, which also sometimes comes with losing the benefit and ease of use of nice utility packages like drizzle-zod
since they output schemas using zod's primitives rather than zod-form-data's.
More interestingly, another approach is to give hints to the expected data type using a custom syntax in the field names. I recently came across a simple package (parse-nested-form-data) and found the idea to be quite elegant. This could probably easily be plugged into superValidate using a custom resolver wrapper.