Skip to content

Commit 269f233

Browse files
committed
Client validation now runs properly when $form is modified.
1 parent 0338a6d commit 269f233

File tree

6 files changed

+214
-46
lines changed

6 files changed

+214
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Fixed timing issues with radio buttons and validation with side-effects.
1414
- Using standard array access to fix problems with iOS and iPadOS <= 15.3
1515
- Fixed client validation problems with multi-select fields.
16+
- Client validation now runs properly when `$form` is modified.
1617

1718
## [0.8.4] - 2023-04-27
1819

src/lib/client/index.ts

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,44 @@ export function superForm<
609609
return obj === true;
610610
}
611611

612+
function Tainted__validate(path: string[], taint: TaintOption) {
613+
if (
614+
options.validationMethod == 'onblur' ||
615+
options.validationMethod == 'submit-only'
616+
) {
617+
return;
618+
}
619+
620+
let shouldValidate = options.validationMethod === 'oninput';
621+
622+
if (!shouldValidate) {
623+
const errorContent = get(Errors);
624+
625+
const errorNode = errorContent
626+
? pathExists(errorContent, path)
627+
: undefined;
628+
629+
// Need a special check here, since if the error has never existed,
630+
// there won't be a key for the error. But if it existed and was cleared,
631+
// the key exists with the value undefined.
632+
const hasError = errorNode && errorNode.key in errorNode.parent;
633+
634+
shouldValidate = !!hasError;
635+
}
636+
637+
if (shouldValidate) {
638+
validateField(
639+
path,
640+
options.validators,
641+
options.defaultValidator,
642+
Form,
643+
Errors,
644+
Tainted,
645+
{ taint }
646+
);
647+
}
648+
}
649+
612650
function Tainted_update(
613651
newObj: unknown,
614652
compareAgainst: unknown,
@@ -634,6 +672,11 @@ export function superForm<
634672
setPaths(tainted, paths, options === true ? true : undefined);
635673
return tainted;
636674
});
675+
676+
for (const path of paths) {
677+
//console.log('🚀 ~ file: index.ts:681 ~ path:', path);
678+
Tainted__validate(path, options);
679+
}
637680
}
638681
}
639682

@@ -1264,51 +1307,6 @@ function formEnhance<T extends AnyZodObject, M>(
12641307
}
12651308
formEl.addEventListener('focusout', checkBlur);
12661309

1267-
// Add input event, to check tainted
1268-
async function checkInput(e: Event) {
1269-
if (
1270-
options.validationMethod == 'onblur' ||
1271-
options.validationMethod == 'submit-only'
1272-
) {
1273-
return;
1274-
}
1275-
1276-
// Some form fields have some timing issue, need to wait
1277-
if (timingIssue(e.target)) {
1278-
await new Promise((r) => setTimeout(r, 0));
1279-
}
1280-
1281-
const errorContent = get(errors);
1282-
const taintedContent = get(tainted);
1283-
1284-
for (const change of get(lastChanges)) {
1285-
let shouldValidate = options.validationMethod === 'oninput';
1286-
1287-
if (!shouldValidate) {
1288-
const isTainted =
1289-
taintedContent &&
1290-
pathExists(taintedContent, change, (value) => value === true);
1291-
1292-
const errorNode = errorContent
1293-
? pathExists(errorContent, change)
1294-
: undefined;
1295-
1296-
// Need a special check here, since if the error has never existed,
1297-
// there won't be a key for the error. But if it existed and was cleared,
1298-
// the key exists with the value undefined.
1299-
const hasError = errorNode && errorNode.key in errorNode.parent;
1300-
1301-
shouldValidate = !!isTainted && !!hasError;
1302-
}
1303-
1304-
if (shouldValidate) {
1305-
//console.log('🚀 ~ file: index.ts:920 ~ INPUT with error:', change);
1306-
validateChange(change);
1307-
}
1308-
}
1309-
}
1310-
formEl.addEventListener('input', checkInput);
1311-
13121310
const ErrorTextEvents = new Set<HTMLFormElement>();
13131311

13141312
function ErrorTextEvents_selectText(e: Event) {
@@ -1338,7 +1336,6 @@ function formEnhance<T extends AnyZodObject, M>(
13381336
);
13391337
ErrorTextEvents.clear();
13401338
formEl.removeEventListener('focusout', checkBlur);
1341-
formEl.removeEventListener('input', checkInput);
13421339
});
13431340

13441341
type ValidationResponse<
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Actions, PageServerLoad } from './$types';
2+
import { superValidate } from '$lib/server';
3+
import type { z } from 'zod';
4+
import { schema } from './schemas';
5+
6+
export const load = (async () => {
7+
const form = await superValidate(schema);
8+
return { form };
9+
}) satisfies PageServerLoad;
10+
11+
export const actions = {
12+
default: async ({ request }) => {
13+
const formData = await request.formData();
14+
const form = await superValidate(formData, schema);
15+
console.log('POST', form);
16+
17+
return { form };
18+
}
19+
} satisfies Actions;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<script lang="ts">
2+
import { superForm } from '$lib/client';
3+
import type { PageData } from './$types';
4+
import SuperDebug from '$lib/client/SuperDebug.svelte';
5+
import { schema } from './schemas';
6+
import { page } from '$app/stores';
7+
import InputField from './input-field.svelte';
8+
9+
export let data: PageData;
10+
11+
const { form, errors, message, tainted, enhance } = superForm(data.form, {
12+
dataType: 'json',
13+
defaultValidator: 'keep',
14+
validators: schema,
15+
onError: (event) => {
16+
console.log('onError', event);
17+
},
18+
onResult: (event) => {
19+
console.log('onResult', event);
20+
},
21+
onSubmit: (args) => {
22+
console.log('onSubmit', args);
23+
}
24+
});
25+
26+
$: {
27+
//console.log('current form', $form, 'errors', $errors);
28+
}
29+
</script>
30+
31+
<SuperDebug data={{ $form, $errors, $tainted }} />
32+
33+
<h3>Superforms testing ground</h3>
34+
35+
{#if $message}
36+
<div
37+
class="status"
38+
class:error={$page.status >= 400}
39+
class:success={$page.status == 200}
40+
>
41+
{$message}
42+
</div>
43+
{/if}
44+
45+
{#if $errors}
46+
<div
47+
class="status"
48+
class:error={$page.status >= 400}
49+
class:success={$page.status == 200}
50+
>
51+
ERRORS
52+
{JSON.stringify($errors, null, 2)}
53+
</div>
54+
{/if}
55+
<form method="POST" enctype="multipart/form-data" use:enhance>
56+
<InputField label="Name" bind:value={$form.name} errors={$errors.name} />
57+
<br />
58+
<label>
59+
Email<br />
60+
<input
61+
name="email"
62+
type="email"
63+
data-invalid={$errors.email}
64+
bind:value={$form.email}
65+
/>
66+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
67+
</label>
68+
<br />
69+
70+
<button>Submit</button>
71+
</form>
72+
73+
<hr />
74+
<p>
75+
<a target="_blank" href="https://superforms.vercel.app/api"
76+
>API Reference</a
77+
>
78+
</p>
79+
80+
<style>
81+
.invalid {
82+
color: red;
83+
}
84+
85+
.status {
86+
color: white;
87+
padding: 4px;
88+
padding-left: 8px;
89+
border-radius: 2px;
90+
font-weight: 500;
91+
}
92+
93+
.status.success {
94+
background-color: seagreen;
95+
}
96+
97+
.status.error {
98+
background-color: #ff2a02;
99+
}
100+
101+
input {
102+
background-color: #ddd;
103+
}
104+
105+
a {
106+
text-decoration: underline;
107+
}
108+
109+
hr {
110+
margin-top: 4rem;
111+
}
112+
113+
form {
114+
padding-top: 1rem;
115+
padding-bottom: 1rem;
116+
}
117+
</style>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script lang="ts">
2+
//export let field
3+
export let value: string;
4+
export let errors: string[] | undefined;
5+
export let label: string;
6+
</script>
7+
8+
<label>
9+
{label}<br />
10+
<input type="text" data-invalid={errors} bind:value />
11+
</label>
12+
{#if errors}<span class="invalid">{errors}</span>{/if}
13+
14+
<button
15+
on:click={(e) => {
16+
e.preventDefault();
17+
value = 'I am valid';
18+
}}>reset</button
19+
>
20+
21+
<style lang="scss">
22+
.invalid {
23+
color: orangered;
24+
}
25+
</style>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
name: z
5+
.string()
6+
.min(2)
7+
.regex(/^[A-Z]/, 'should start with a capital letter'),
8+
email: z.string().email()
9+
});

0 commit comments

Comments
 (0)