Un fichier `+page.server.js` peut exporter des _actions_, ce qui vous permet d'envoyer des données avec `POST` vers le serveur en utilisant un élément de formulaire `
`. Lorsque vous utilisez un élément ``, la présence de JavaScript côté client est optionnelle, mais vous pouvez facilement _améliorer progressivement_ vos interactions de formulaire avec JavaScript pour fournir la meilleure expérience utilisateur. ## Actions par défaut [!VO]Default actions Dans le cas le plus simple, une page déclare une action par défaut appelée `default` : ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO identifier l'utilisateur } }; ``` Pour invoquer cette action depuis la page `/login`, ajouter simplement un élément `` — aucun JavaScript n'est nécessaire : ```svelte
``` Lorsque le bouton est cliqué, le navigateur va envoyer au serveur les données du formulaire via une requête `POST`, en exécutant l'action par défaut. > [!NOTE] Les actions utilisent toujours des requêtes `POST`, puisque les requêtes `GET` ne sont pas > censées avoir d'effets de bord. Nous pouvons aussi invoquer l'action depuis d'autres pages (par exemple, s'il y a un widget de connexion dans la barre de navigation du layout racine) en ajoutant l'attribut `action` pointant vers la page : ```html /// file: src/routes/+layout.svelte
``` ## Actions nommées [!VO]Named actions Plutôt qu'une seule action `default` par défaut, une page peut avoir autant d'actions nommées que nécessaire : ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { --- default: async (event) => {--- +++ login: async (event) => {+++ // TODO connecter l'utilisateur }, +++ register: async (event) => { // TODO inscrire l'utilisateur }+++ }; ``` Pour invoquer une action nommée, ajoutez un paramètre de requête comportant le nom de l'action préfixé par un caractère `/` : ```svelte
``` ```svelte ``` Similaire à l'attribut `action`, nous pouvons utiliser l'attribut `formaction` sur un bouton pour envoyer avec `POST` les mêmes données de formulaire à une action différente de celle de l'élément `` parent : ```svelte /// file: src/routes/login/+page.svelte ++++++
``` > [!NOTE] Il n'est pas possible d'avoir des actions par défaut dans le même fichier que des actions > nommées, car si vous ciblez une action nommée avec POST sans redirection, le paramètre de requête > est persisté dans l'URL, ce qui signifie que la prochaine requête POST par défaut va cibler > l'action nommée précédente. ## Anatomie d'une action [!VO]Anatomy of an action Chaque action reçoit un objet `RequestEvent`, vous permettant de lire les données avec `request.formData()`. Après le traitement de la requête (par exemple, pour connecter l'utilisateur ou l'utilisatrice en définissant un cookie), l'action peut répondre avec des données qui seront disponibles via la propriété `form` sur la page correspondante, et via `page.form` dans toute l'application, jusqu'à sa prochaine mise à jour. ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import * as db from '$lib/server/db'; /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO inscrire l'utilisateur } }; ``` ```svelte {#if form?.success}

Connexion réussie ! Content de vous revoir, {data.user.name}

{/if} ``` > [!LEGACY] > `PageProps` a été ajouté dans la version 2.16.0 de SvelteKit. Dans les versions antérieures, vous > deviez typer les propriétés `data` et `form` individuellement : > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ > let { data, form } = $props(); > ``` > > Avec Svelte 4, vous utiliseriez plutôt `export let data` et `export let form` pour déclarer les > propriétés. ### Erreurs de validation [!VO]Validation errors Si la requête n'a pas pu être traitée à cause de données non valides, vous pouvez renvoyer des erreurs de validation — en plus des valeurs précédemment soumises par le formulaire — à l'utilisateur ou l'utilisatrice afin qu'il ou elle ré-essaye. La fonction `fail` vous permet de renvoyer un statut HTTP (généralement 400 ou 422, dans le cas d'erreurs de validation) avec les données. Le statut est rendu disponible via `page.status` et les données via `form` : ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- +++import { fail } from '@sveltejs/kit';+++ import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); +++ if (!email) { return fail(400, { email, missing: true }); }+++ const user = await db.getUser(email); +++ if (!user || user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); }+++ cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO inscrire l'utilisateur } }; ``` > [!NOTE] Notez que par précaution, nous renvoyons uniquement l'email à la page — pas le mot de > passe. ```svelte /// file: src/routes/login/+page.svelte
+++ {#if form?.missing}

Le champ email est requis

{/if} {#if form?.incorrect}

Identifiants invalides !

{/if}+++
``` Les données renvoyées doivent être sérialisables en JSON. À part ça, la structure est complètement libre. Par exemple, si vous avez plusieurs formulaires sur la même page, vous pourriez différencier à quel élément `
` les données `form` font référence avec une propriété `id` ou similaire. ### Redirections [!VO]Redirects Les redirections (et les erreurs) fonctionnent exactement de la même façon que dans [`load`](load#Redirects) : ```js // @errors: 2345 /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import { fail, +++redirect+++ } from '@sveltejs/kit'; import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request, +++url+++ }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); if (!user) { return fail(400, { email, missing: true }); } if (user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); +++ if (url.searchParams.has('redirectTo')) { redirect(303, url.searchParams.get('redirectTo')); }+++ return { success: true }; }, register: async (event) => { // TODO inscrire l'utilisateur } }; ``` ## Chargement de données [!VO]Loading data Après l'exécution d'une action, la page sera re-rendue (à moins qu'une redirection ou une erreur inattendue se produise), avec la valeur de retour de l'action rendue disponible dans la page en tant que propriété `form`. Ceci signifie que les fonctions `load` de la page vont être exécutées après la fin de l'exécution d'une action. Notez que `handle` est exécuté avant l'invocation de l'action, et n'est pas ré-exécutée avant les fonctions `load`. Ceci signifie que si, par exemple, vous utiliser `handle` pour remplir `event.locals` en fonction d'un cookie, vous devez mettre à jour `event.locals` lorsque vous définissez ou supprimez le cookie lors d'une action : ```js /// file: src/hooks.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: global.d.ts declare global { function getUser(sessionid: string | undefined): { name: string; }; } export {}; // @filename: index.js // ---cut--- /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUser(event.cookies.get('sessionid')); return resolve(event); } ``` ```js /// file: src/routes/account/+page.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: index.js // ---cut--- /** @type {import('./$types').PageServerLoad} */ export function load(event) { return { user: event.locals.user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { logout: async (event) => { event.cookies.delete('sessionid', { path: '/' }); event.locals.user = null; } }; ``` ## Amélioration progressive [!VO]Progressive enhancement Dans les sections précédentes, nous avons construit une action `/login` qui [fonctionne sans JavaScript côté client](https://kryogenix.org/code/browser/everyonehasjs.html) — aucun `fetch` n'a été impliqué. C'est super, mais lorsque JavaScript _est_ disponible, nous pouvons améliorer progressivement nos interactions de formulaire pour fournir une meilleure expérience utilisateur. ### use:enhance La manière la plus simple d'améliorer un formulaire progressivement est d'ajouter l'action `use:action` : ```svelte /// file: src/routes/login/+page.svelte ``` > [!NOTE] `use:enhance` peut uniquement être utilisé avec des formulaires qui ont `method="POST"` et > pointent vers des actions définies dans un fichier `+page.server.js`. Cela ne fonctionnera pas > avec `method="GET"`, qui est le fonctionnement par défaut pour des formulaires sans méthode > précisée. Utiliser `use:enhance` sur des formulaires sans `method="POST"` ou cibler un endpoint > `+server.js` va provoquer une erreur. > [!NOTE] Oui, il peut y avoir de la confusion entre l'action `enhance` et ``, qui sont > toutes les deux appelées "action". Cette documentation est pleine d'actions. Désolé. Sans argument, `use:enhance` va émuler le comportement natif du navigateur, simplement sans le rechargement complet de la page. Plus précisément, cela va : - mettre à jour la propriété `form`, ainsi que `page.form` et `page.status` lors d'une réponse réussie ou invalide, mais uniquement si l'action est sur la même page que celle depuis laquelle vous soumettez. Par exemple, si vous formulaire ressemble à ``, la propriété `form` et l'état `page.form` ne seront _pas_ mis à jour. En effet, la soumission native d'un formulaire vous redirige vers la page sur laquelle l'action est définie. Si vous souhaitez tout de même les mettre à jour, vous devez utiliser [`applyAction`](#Progressive-enhancement-Customising-use:enhance) - réinitialiser l'élément `` - invalider toutes les données avec `invalidateAll` si la réponse est réussie - appeler `goto` si la réponse est une redirection - afficher la frontière d'erreur `+error` la plus proche si une erreur se produit - [réinitialiser le focus](accessibility#Focus-management) sur l'élément approprié ### Personnaliser use:enhance [!VO]Customising use:enhance Pour personnaliser le comportement, vous pouvez fournir une `SubmitFunction` qui sera exécutée immédiatement avant la soumission du formulaire, et (optionnellement) renvoyer un callback qui fournira une `ActionResult`. Notez que si vous renvoyez un callback, le comportement par défaut mentionné plus haut sera ignoré. Pour récupérer ce comportement, vous devez exécuter `update`. ```svelte { // `formElement` est l'élément `` // `formData` est son objet `FormData` qui est sur le point d'être soumis // `action` est l'URL vers laquelle le formulaire pointe // exécuter `cancel()` va empêcher la soumission // `submitter` est l'élément `HTMLElement` qui a provoqué la soumission du formulaire return async ({ result, update }) => { // `result` est un objet `ActionResult` // `update` est une fonction qui déclenche la logique qui aurait été exécutée par défaut si ce // callback n'avait pas été défini }; }} > ``` Vous pouvez utiliser ces fonctions pour afficher et cacher des éléments d'interface de chargement, entre autres. Si vous renvoyez un callback, vous pourriez avoir besoin de reproduire en partie le comportement par défaut de `use:enhance`, mais sans invalider toutes les données lors d'une réponse réussie. Vous pouvez faire cela grâce à `applyAction` : ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` est un objet `ActionResult` +++ if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); }+++ }; }} > ``` Le comportement de `applyAction(result)` dépend de `result.type` : - `success`, `failure` — définit `page.status` en tant que `result.status` et met à jour `form` et `page.form` en tant que `result.data` (indépendamment de la source de soumission, à la différence du `update` venant de `enhance`) - `redirect` — exécute `goto(result.location, { invalidateAll: true })` - `error` — affiche la frontière `+error` la plus proche en utilisant `result.error` Dans tous les cas, [le focus sera réinitialisé](accessibility#Focus-management). ### Gestionnaire personnalisé d'évènement [VO]Custom event listener Nous pouvons également implémenter de l'amélioration progressive nous-même, sans `use:enhance`, avec un gestionnaire d'évènement normal sur l'élément `` : ```svelte
``` Notez que vous avez besoin de `deserialize` la réponse avant de la traiter en utilisant la méthode correspondante importée depuis `$app/forms`. `JSON.parse()` ne suffit pas car les actions de formulaires — comme les fonctions `load` — supportent également le renvoi d'objets `Date` ou `BigInt`. Si vous avez un fichier `+server.js` associé à votre `+page.server.js`, les requêtes `fetch` vont y être dirigées par défaut. Si vous souhaitez envoyer une requête `POST` à une action de `+page.server.js`, utilisez l'en-tête personnalisée `x-sveltekit-action` : ```js const response = await fetch(this.action, { method: 'POST', body: data, +++ headers: { 'x-sveltekit-action': 'true' }+++ }); ``` ## Alternatives Les actions de formulaire sont la méthode recommandée pour envoyer des données au serveur, puisqu'elles peuvent être améliorées progressivement, mais vous pouvez également utiliser des fichiers [`+server.js`](routing#server) pour exposer (par exemple) une API JSON. Voici ce à quoi une telle interaction pourrait ressembler : ```svelte ``` ```js // @errors: 2355 1360 2322 /// file: src/routes/api/ci/+server.js /** @type {import('./$types').RequestHandler} */ export function POST() { // faire quelque chose } ``` ## GET vs POST Comme nous venons de le voir, pour invoquer une action de formulaire, vous devez utiliser `method="POST"`. Certains formulaires n'ont pas besoin d'utiliser `POST` pour envoyer des données au serveur — les inputs de recherche par exemple. Pour ces cas-là, vous pouvez utiliser `method="GET"` (ou de manière équivalente, aucune `method`), et SvelteKit va traiter ces requêtes comme si elles venaient d'éléments ``, utilisant le routeur client plutôt qu'une navigation impliquant le rechargement complet de la page : ```html
``` Soumettre ce formulaire va déclencher une navigation vers `/search?q=...` et invoquer votre fonction `load`, mais ne va invoquer aucune action. Comme pour les éléments `
`, vous pouvez définir les attributs [`data-sveltekit-reload`](link-options#data-sveltekit-reload), [`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate), [`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) et [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) sur l'élément `
` pour contrôler le comportement du routeur. ## Sur le même sujet - [Tutoriel : Formulaires](/tutorial/kit/the-form-element)