Im Kern der Funktionalität von TanStack Form steht das Konzept der Validierung. TanStack Form macht die Validierung hochgradig anpassbar
Das liegt ganz bei Ihnen! Die <form.Field /> Komponente akzeptiert einige Callbacks als Props wie onChange oder onBlur. Diese Callbacks erhalten den aktuellen Wert des Feldes sowie das fieldAPI-Objekt übergeben, sodass Sie die Validierung durchführen können. Wenn Sie einen Validierungsfehler finden, geben Sie einfach die Fehlermeldung als String zurück und sie wird unter field.state.meta.errors verfügbar sein.
Hier ist ein Beispiel
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Im obigen Beispiel erfolgt die Validierung bei jedem Tastendruck (onchange). Wenn wir stattdessen wollten, dass die Validierung beim Ausblenden des Feldes erfolgt, würden wir den obigen Code wie folgt ändern
<form.Field
name="age"
validators={{
onBlur: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onBlur: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Sie können also steuern, wann die Validierung durchgeführt wird, indem Sie die gewünschte Rückruffunktion implementieren. Sie können sogar zu verschiedenen Zeiten unterschiedliche Validierungsstücke durchführen
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
onBlur: ({ value }) => (value < 0 ? 'Invalid value' : undefined),
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
onBlur: ({ value }) => (value < 0 ? 'Invalid value' : undefined),
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Im obigen Beispiel validieren wir unterschiedliche Dinge für dasselbe Feld zu unterschiedlichen Zeiten (bei jedem Tastendruck und beim Verlassen des Feldes). Da field.state.meta.errors ein Array ist, werden alle relevanten Fehler zu einem bestimmten Zeitpunkt angezeigt. Sie können auch field.state.meta.errorMap verwenden, um Fehler basierend darauf zu erhalten, *wann* die Validierung durchgeführt wurde (onChange, onBlur etc.). Mehr Informationen zur Anzeige von Fehlern weiter unten.
Sobald Ihre Validierung eingerichtet ist, können Sie die Fehler aus einem Array abbilden, um sie in Ihrer Benutzeroberfläche anzuzeigen
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<!-- ... -->
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<!-- ... -->
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Oder verwenden Sie die Eigenschaft errorMap, um auf den spezifischen Fehler zuzugreifen, den Sie suchen
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<!-- ... -->
{#if field.state.meta.errorMap['onChange']}
<em role="alert">{field.state.meta.errorMap['onChange']}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{#snippet children(field)}
<!-- ... -->
{#if field.state.meta.errorMap['onChange']}
<em role="alert">{field.state.meta.errorMap['onChange']}</em>
{/if}
{/snippet}
</form.Field>
Es ist erwähnenswert, dass unser errors Array und die errorMap den von den Validatoren zurückgegebenen Typen entsprechen. Das bedeutet
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
}}
>
{#snippet children(field)}
<!-- ... -->
<!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
<!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
{#if field.state.meta.errorMap['onChange']?.isOldEnough}
<em>The user is not old enough</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
}}
>
{#snippet children(field)}
<!-- ... -->
<!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
<!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
{#if field.state.meta.errorMap['onChange']?.isOldEnough}
<em>The user is not old enough</em>
{/if}
{/snippet}
</form.Field>
Wie oben gezeigt, akzeptiert jedes <form.Field> seine eigenen Validierungsregeln über die Callbacks onChange, onBlur usw. Es ist auch möglich, Validierungsregeln auf Formular-Ebene zu definieren (im Gegensatz zu Feld für Feld), indem ähnliche Callbacks an den createForm() Hook übergeben werden.
Beispiel
<script>
import { createForm } from '@tanstack/svelte-form'
const form = createForm(() => ({
defaultValues: {
age: 0,
},
onSubmit: async ({ value }) => {
console.log(value)
},
validators: {
// Add validators to the form the same way you would add them to a field
onChange({ value }) {
if (value.age < 13) {
return 'Must be 13 or older to sign'
}
return undefined
},
},
}))
// Subscribe to the form's error map so that updates to it will render
// alternately, you can use `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
</script>
<div>
<!-- ... -->
{#if formErrorMap.current.onChange}
<div>
<em>There was an error on the form: {formErrorMap.current.onChange}</em>
</div>
{/if}
<!-- ... -->
</div>
<script>
import { createForm } from '@tanstack/svelte-form'
const form = createForm(() => ({
defaultValues: {
age: 0,
},
onSubmit: async ({ value }) => {
console.log(value)
},
validators: {
// Add validators to the form the same way you would add them to a field
onChange({ value }) {
if (value.age < 13) {
return 'Must be 13 or older to sign'
}
return undefined
},
},
}))
// Subscribe to the form's error map so that updates to it will render
// alternately, you can use `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
</script>
<div>
<!-- ... -->
{#if formErrorMap.current.onChange}
<div>
<em>There was an error on the form: {formErrorMap.current.onChange}</em>
</div>
{/if}
<!-- ... -->
</div>
Obwohl wir vermuten, dass die meisten Validierungen synchron sein werden, gibt es viele Fälle, in denen ein Netzwerkanruf oder eine andere asynchrone Operation für die Validierung nützlich wäre.
Um dies zu erreichen, haben wir dedizierte onChangeAsync, onBlurAsync und andere Methoden, die zur Validierung verwendet werden können
<form.Field
name="age"
validators={{
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value < 13 ? 'You must be 13 to make an account' : undefined
},
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value < 13 ? 'You must be 13 to make an account' : undefined
},
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Synchrone und asynchrone Validierungen können koexistieren. Es ist beispielsweise möglich, sowohl onBlur als auch onBlurAsync für dasselbe Feld zu definieren
<form.Field
name="age"
validators={{
onBlur: ({ value }) => (value < 13 ? 'You must be at least 13' : undefined),
onBlurAsync: async ({ value }) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? 'You can only increase the age' : undefined
},
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
<form.Field
name="age"
validators={{
onBlur: ({ value }) => (value < 13 ? 'You must be at least 13' : undefined),
onBlurAsync: async ({ value }) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? 'You can only increase the age' : undefined
},
}}
>
{#snippet children(field)}
<label for={field.name}>Age:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
type="number"
onblur={field.handleBlur}
onchange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{#if field.state.meta.errors}
<em role="alert">{field.state.meta.errors.join(', ')}</em>
{/if}
{/snippet}
</form.Field>
Die synchrone Validierungsmethode (onBlur) wird zuerst ausgeführt, und die asynchrone Methode (onBlurAsync) wird nur ausgeführt, wenn die synchrone Methode (onBlur) erfolgreich war. Um dieses Verhalten zu ändern, setzen Sie die Option asyncAlways auf true, und die asynchrone Methode wird unabhängig vom Ergebnis der synchronen Methode ausgeführt.
Während asynchrone Aufrufe der richtige Weg sind, wenn gegen die Datenbank validiert wird, ist das Ausführen einer Netzwerkanfrage bei jedem Tastendruck eine gute Möglichkeit, Ihre Datenbank zu überlasten (DDOS).
Stattdessen ermöglichen wir eine einfache Methode zum Verzögern Ihrer async Aufrufe, indem wir eine einzige Eigenschaft hinzufügen
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
// ...
},
}}
>
<!-- ... -->
</form.Field>
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
// ...
},
}}
>
<!-- ... -->
</form.Field>
Dies verzögert jeden asynchronen Aufruf um 500 ms. Sie können diese Eigenschaft sogar pro Validierungseigenschaft überschreiben
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsyncDebounceMs: 1500,
onChangeAsync: async ({ value }) => {
// ...
},
onBlurAsync: async ({ value }) => {
// ...
},
}}
>
<!-- ... -->
</form.Field>
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsyncDebounceMs: 1500,
onChangeAsync: async ({ value }) => {
// ...
},
onBlurAsync: async ({ value }) => {
// ...
},
}}
>
<!-- ... -->
</form.Field>
Dies führt onChangeAsync alle 1500 ms aus, während onBlurAsync alle 500 ms ausgeführt wird.
Während Funktionen mehr Flexibilität und Anpassungsmöglichkeiten für Ihre Validierung bieten, können sie etwas umständlich sein. Um dieses Problem zu lösen, gibt es Bibliotheken, die eine schema-basierte Validierung bieten, um eine Kurzschrift und Typsicherheit bei der Validierung erheblich zu vereinfachen. Sie können auch ein einzelnes Schema für Ihr gesamtes Formular definieren und es auf Formularebene übergeben, Fehler werden automatisch an die Felder weitergeleitet.
TanStack Form unterstützt nativ alle Bibliotheken, die der Standard Schema-Spezifikation folgen, insbesondere
Hinweis: Stellen Sie sicher, dass Sie die neueste Version der Schema-Bibliotheken verwenden, da ältere Versionen Standard Schema möglicherweise noch nicht unterstützen.
Um Schemata aus diesen Bibliotheken zu verwenden, können Sie sie an die validators Props übergeben, so wie Sie es auch mit einer benutzerdefinierten Funktion tun würden
<script>
import { z } from 'zod'
// ...
const form = createForm(() => ({
// ...
}))
</script>
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}}
>
<!-- ... -->
</form.Field>
<script>
import { z } from 'zod'
// ...
const form = createForm(() => ({
// ...
}))
</script>
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}}
>
<!-- ... -->
</form.Field>
Asynchrone Validierungen auf Formular- und Feld-Ebene werden ebenfalls unterstützt
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
onChangeAsyncDebounceMs: 500,
onChangeAsync: z.number().refine(
async (value) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value >= currentAge
},
{
message: 'You can only increase the age',
},
),
}}
>
<!-- ... -->
</form.Field>
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
onChangeAsyncDebounceMs: 500,
onChangeAsync: z.number().refine(
async (value) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value >= currentAge
},
{
message: 'You can only increase the age',
},
),
}}
>
<!-- ... -->
</form.Field>
Die Rückruffunktionen onChange, onBlur usw. werden auch bei der Übermittlung des Formulars ausgeführt, und die Übermittlung wird blockiert, wenn das Formular ungültig ist.
Das Formularstatus-Objekt verfügt über ein Flag canSubmit, das `false` ist, wenn ein Feld ungültig ist und das Formular berührt wurde (canSubmit ist `true`, bis das Formular berührt wurde, auch wenn einige Felder "technisch" ungültig sind, basierend auf ihren onChange/onBlur Props).
Sie können es über form.Subscribe abonnieren und den Wert verwenden, um beispielsweise die Submit-Schaltfläche zu deaktivieren, wenn das Formular ungültig ist (in der Praxis sind deaktivierte Schaltflächen nicht zugänglich, verwenden Sie stattdessen aria-disabled).
<script>
import { createForm } from '@tanstack/svelte-form'
const form = createForm(() => ({
/* ... */
}))
</script>
<!-- ... -->
<!-- Dynamic submit button -->
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)}
>
{#snippet children(state)}
<button type="submit" disabled={!state.canSubmit}>
{state.isSubmitting ? '...' : 'Submit'}
</button>
{/snippet}
</form.Subscribe>
<script>
import { createForm } from '@tanstack/svelte-form'
const form = createForm(() => ({
/* ... */
}))
</script>
<!-- ... -->
<!-- Dynamic submit button -->
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)}
>
{#snippet children(state)}
<button type="submit" disabled={!state.canSubmit}>
{state.isSubmitting ? '...' : 'Submit'}
</button>
{/snippet}
</form.Subscribe>
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.