Skip to content

Commit 37b7b93

Browse files
committed
Add support for form arrays
1 parent 992b693 commit 37b7b93

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
lines changed

src/lib/client/superForm.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,30 @@ try {
389389
// No Storybook
390390
}
391391

392+
const lifeCycleCallbacks = {
393+
onDestroy: new Set<() => void>(),
394+
beforeNavigate: new Set<(nav: BeforeNavigate) => Promise<void>>()
395+
}
396+
let componentInitialized = false;
397+
function initLifeCycleCallbacks() {
398+
if (componentInitialized) return;
399+
componentInitialized = true;
400+
401+
onDestroy(() => {
402+
for (const callback of lifeCycleCallbacks.onDestroy) {
403+
callback();
404+
}
405+
lifeCycleCallbacks.onDestroy.clear();
406+
});
407+
408+
beforeNavigate((nav: BeforeNavigate) => {
409+
for (const callback of lifeCycleCallbacks.beforeNavigate) {
410+
callback(nav);
411+
}
412+
lifeCycleCallbacks.beforeNavigate.clear();
413+
});
414+
}
415+
392416
/////////////////////////////////////////////////////////////////////
393417

394418
/**
@@ -410,6 +434,8 @@ export function superForm<
410434
// To check if a full validator is used when switching options.validators dynamically
411435
let initialValidator: FormOptions<T, M, In>['validators'] | undefined = undefined;
412436

437+
initLifeCycleCallbacks();
438+
413439
{
414440
if (options.legacy ?? LEGACY_MODE) {
415441
if (options.resetForm === undefined) options.resetForm = false;
@@ -525,7 +551,7 @@ export function superForm<
525551

526552
///// From here, form is properly initialized /////
527553

528-
onDestroy(() => {
554+
lifeCycleCallbacks.onDestroy.add(() => {
529555
Unsubscriptions_unsubscribe();
530556
NextChange_clear();
531557
EnhancedForm_destroy();
@@ -1397,7 +1423,7 @@ export function superForm<
13971423
///// Store subscriptions ///////////////////////////////////////////////////
13981424

13991425
if (browser) {
1400-
beforeNavigate(Tainted_check);
1426+
lifeCycleCallbacks.beforeNavigate.add(Tainted_check);
14011427

14021428
// Need to subscribe to catch page invalidation.
14031429
Unsubscriptions_add(

src/routes/(v2)/v2/Navigation.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
'issue-337-checkboxes',
1515
'issue-345',
1616
'letters',
17+
'multiple-forms',
1718
'multiple-files',
1819
'multistep-client',
1920
'multistep-server',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { zod } from '$lib/adapters/zod.js';
2+
import { message, superValidate } from '$lib/server/index.js';
3+
import { type Actions, fail } from '@sveltejs/kit';
4+
import { schema } from './schema.js';
5+
6+
const items = [
7+
{
8+
id: 1,
9+
label: 'One'
10+
},
11+
{
12+
id: 2,
13+
label: 'Two'
14+
},
15+
{
16+
id: 3,
17+
label: 'Three'
18+
}
19+
];
20+
21+
export const load = async () => {
22+
const item_forms = await Promise.all(
23+
items.map((item) =>
24+
superValidate(item, zod(schema), {
25+
id: item.id.toString()
26+
})
27+
)
28+
);
29+
30+
return { item_forms };
31+
};
32+
33+
export const actions: Actions = {
34+
async create() {
35+
items.push({
36+
id: items.length + 1,
37+
label: (items.length + 1).toString()
38+
});
39+
40+
return { success: true };
41+
},
42+
43+
async save({ request }) {
44+
const form = await superValidate(request, zod(schema));
45+
46+
if (!form.valid) {
47+
// Again, return { form } and things will just work.
48+
return fail(400, { form });
49+
}
50+
51+
const index = items.findIndex((item) => item.id === form.data.id);
52+
if(index !== -1) {
53+
items[index].label = form.data.label;
54+
}
55+
56+
return message(form, `Item ${form.data.id} updated`);
57+
}
58+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script lang="ts">
2+
import type { PageData } from './$types.js';
3+
import { superForm } from '$lib/client/index.js';
4+
import Form from './Form.svelte';
5+
import { enhance } from '$app/forms';
6+
7+
let {
8+
data
9+
}: {
10+
data: PageData;
11+
} = $props();
12+
13+
const superforms = $derived(data.item_forms.map(item_form => superForm(item_form, {
14+
id: item_form.id,
15+
dataType: 'json'
16+
})));
17+
</script>
18+
19+
{#each superforms as superform (superform.formId)}
20+
<Form {superform} />
21+
<hr>
22+
{/each}
23+
24+
<form action="?/create" method=post use:enhance>
25+
<button type=submit>Add</button>
26+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script lang="ts">
2+
import type { SuperForm } from '$lib/index.js';
3+
import type { schema } from './schema.js';
4+
import { z } from 'zod';
5+
import SuperDebug from '$lib/client/SuperDebug.svelte';
6+
7+
let { superform }: {
8+
superform: SuperForm<z.infer<typeof schema>>;
9+
} = $props();
10+
11+
let { form, message, enhance } = superform;
12+
</script>
13+
14+
<form action="?/save" method="post" use:enhance>
15+
<input type="hidden" name="id" value={$form.id} />
16+
17+
<SuperDebug data={$form} />
18+
19+
<label>
20+
<input type="text" name="label" bind:value={$form.label} />
21+
</label>
22+
23+
<button type="submit">Save</button>
24+
25+
{#if $message}
26+
<p>{$message}</p>
27+
{/if}
28+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
id: z.number(),
5+
label: z.string()
6+
});

0 commit comments

Comments
 (0)