Skip to content

Commit b579caf

Browse files
committed
Added applyAction: 'never'
Added invalidateAll: 'pessimistic'
1 parent 90356fc commit b579caf

File tree

5 files changed

+159
-12
lines changed

5 files changed

+159
-12
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Added `applyAction: 'never'` option, to prevent load function [invalidation](https://svelte.dev/tutorial/kit/invalidation) from overwriting the form data.
13+
- Added `invalidateAll: 'pessimistic'` option as an alternative to the `'force'` option (recommended to use instead for clarity).
14+
815
## [2.24.1] - 2025-04-06
916

1017
### Fixed

src/lib/client/superForm.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,13 @@ export type FormOptions<
8282
In extends Record<string, unknown> = T
8383
> = Partial<{
8484
id: string;
85-
applyAction: boolean;
86-
invalidateAll: boolean | 'force';
85+
/**
86+
* If `false`, the form won't react to page state updates, except for page invalidations.
87+
* If `'never'`, not even page invalidations will affect the form.
88+
* @default true
89+
*/
90+
applyAction: boolean | 'never';
91+
invalidateAll: boolean | 'force' | 'pessimistic';
8792
resetForm: boolean | (() => boolean);
8893
scrollToError: 'auto' | 'smooth' | 'off' | boolean | ScrollIntoViewOptions;
8994
autoFocusOnError: boolean | 'detect';
@@ -997,16 +1002,20 @@ export function superForm<
9971002
};
9981003
}
9991004

1000-
async function Form_updateFromValidation(form: SuperValidated<T, M, In>, successResult: boolean) {
1001-
if (form.valid && successResult && Form_shouldReset(form.valid, successResult)) {
1002-
Form_reset({ message: form.message, posted: true });
1005+
async function Form_updateFromValidation(
1006+
form2: SuperValidated<T, M, In>,
1007+
successResult: boolean
1008+
) {
1009+
if (form2.valid && successResult && Form_shouldReset(form2.valid, successResult)) {
1010+
Form_reset({ message: form2.message, posted: true });
10031011
} else {
10041012
rebind({
1005-
form,
1013+
form: form2,
10061014
untaint: successResult,
10071015
keepFiles: true,
10081016
// Check if the form data should be used for updating, or if the invalidateAll load function should be used:
1009-
skipFormData: options.invalidateAll == 'force'
1017+
pessimisticUpdate:
1018+
options.invalidateAll == 'force' || options.invalidateAll == 'pessimistic'
10101019
});
10111020
}
10121021

@@ -1017,7 +1026,7 @@ export function superForm<
10171026

10181027
// But do not await on onUpdated itself, since we're already finished with the request
10191028
for (const event of formEvents.onUpdated) {
1020-
event({ form });
1029+
event({ form: form2 });
10211030
}
10221031
}
10231032

@@ -1429,7 +1438,7 @@ export function superForm<
14291438
message?: M;
14301439
keepFiles?: boolean;
14311440
posted?: boolean;
1432-
skipFormData?: boolean;
1441+
pessimisticUpdate?: boolean;
14331442
resetted?: boolean;
14341443
}) {
14351444
//console.log('🚀 ~ file: superForm.ts:721 ~ rebind ~ form:', form.data); //debug
@@ -1444,7 +1453,7 @@ export function superForm<
14441453
// Prevents object errors from being revalidated after rebind.
14451454
// Check if form was invalidated (usually with options.invalidateAll) to prevent data from being
14461455
// overwritten by the load function data
1447-
if (opts.skipFormData !== true) {
1456+
if (!opts.pessimisticUpdate) {
14481457
Form_set(form.data, {
14491458
taint: 'ignore',
14501459
keepFiles: opts.keepFiles
@@ -1518,7 +1527,11 @@ export function superForm<
15181527
initialForms.set(newForm, newForm);
15191528
await Form_updateFromValidation(newForm as SuperValidated<T, M, In>, successResult);
15201529
}
1521-
} else if (pageUpdate.data && typeof pageUpdate.data === 'object') {
1530+
} else if (
1531+
options.applyAction !== 'never' &&
1532+
pageUpdate.data &&
1533+
typeof pageUpdate.data === 'object'
1534+
) {
15221535
// It's a page reload, redirect or error/failure,
15231536
// so don't trigger any events, just update the data.
15241537
for (const newForm of Context_findValidationForms(pageUpdate.data)) {
@@ -1528,7 +1541,7 @@ export function superForm<
15281541
continue;
15291542
}
15301543

1531-
if (options.invalidateAll === 'force') {
1544+
if (options.invalidateAll === 'force' || options.invalidateAll === 'pessimistic') {
15321545
initialForm.data = newForm.data as T;
15331546
}
15341547

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Actions, PageServerLoad } from './$types.js';
2+
3+
import { superValidate, message } from '$lib/index.js';
4+
import { zod } from '$lib/adapters/zod.js';
5+
import { fail } from '@sveltejs/kit';
6+
import { schema } from './schema.js';
7+
8+
export const load: PageServerLoad = async () => {
9+
return { form: await superValidate(zod(schema)), count: Math.random().toString().slice(2) };
10+
};
11+
12+
export const actions: Actions = {
13+
default: async ({ request }) => {
14+
const form = await superValidate(request, zod(schema));
15+
console.log(form);
16+
17+
if (!form.valid) return fail(400, { form });
18+
19+
return message(form, 'Form posted successfully!');
20+
}
21+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<script lang="ts">
2+
import { page } from '$app/state';
3+
import { superForm } from '$lib/index.js';
4+
import SuperDebug from '$lib/index.js';
5+
import { invalidateAll } from '$app/navigation';
6+
7+
let { data } = $props();
8+
9+
$effect(() => {
10+
const interval = setInterval(() => {
11+
console.log('!~!!! here');
12+
invalidateAll();
13+
}, 2000);
14+
return () => clearInterval(interval);
15+
});
16+
17+
const { form, errors, message, enhance } = superForm(data.form, {
18+
applyAction: 'never',
19+
resetForm: true,
20+
taintedMessage: false
21+
});
22+
</script>
23+
24+
<SuperDebug data={$form} />
25+
26+
<h3>Superforms testing ground</h3>
27+
28+
<p>{data.count}</p>
29+
30+
{#if $message}
31+
<!-- eslint-disable-next-line svelte/valid-compile -->
32+
<div class="status" class:error={page.status >= 400} class:success={page.status == 200}>
33+
{$message}
34+
</div>
35+
{/if}
36+
37+
<form method="POST" use:enhance>
38+
<label>
39+
Name<br />
40+
<input name="name" aria-invalid={$errors.name ? 'true' : undefined} bind:value={$form.name} />
41+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
42+
</label>
43+
44+
<label>
45+
Email<br />
46+
<input
47+
name="email"
48+
type="email"
49+
aria-invalid={$errors.email ? 'true' : undefined}
50+
bind:value={$form.email}
51+
/>
52+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
53+
</label>
54+
55+
<button>Submit</button>
56+
</form>
57+
58+
<hr />
59+
<p>
60+
💥 <a target="_blank" href="https://superforms.rocks">Created with Superforms for SvelteKit</a> 💥
61+
</p>
62+
63+
<style>
64+
.invalid {
65+
color: red;
66+
}
67+
68+
.status {
69+
color: white;
70+
padding: 4px;
71+
padding-left: 8px;
72+
border-radius: 2px;
73+
font-weight: 500;
74+
}
75+
76+
.status.success {
77+
background-color: seagreen;
78+
}
79+
80+
.status.error {
81+
background-color: #ff2a02;
82+
}
83+
84+
input {
85+
background-color: #ddd;
86+
}
87+
88+
a {
89+
text-decoration: underline;
90+
}
91+
92+
hr {
93+
margin-top: 4rem;
94+
}
95+
96+
form {
97+
padding-top: 1rem;
98+
padding-bottom: 1rem;
99+
}
100+
</style>
Lines changed: 6 additions & 0 deletions
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+
name: z.string().min(2),
5+
email: z.string().email()
6+
});

0 commit comments

Comments
 (0)