1. Accueil
  2. Articles
19 min de lecture
103 vues

Maîtrisez le NOUVEAU composant `<Form>` d'Inertia.js et routes avec Wayfinder: Optimisez vos projets Laravel avec Vue/React

Image d'illustration pour Maîtrisez le NOUVEAU composant `<Form>` d'Inertia.js et  routes avec Wayfinder: Optimisez vos projets Laravel avec Vue/React

Introduction

Le nouveau composant <Form> d'Inertia.js révolutionne la gestion des formulaires en encapsulant le hook useForm et en automatisant le code répétitif. Il gère automatiquement la validation serveur, les erreurs, l'état de traitement et le téléchargement de fichiers, le tout sans rechargement de page. Cet article vous montre comment l'utiliser efficacement dans vos projets Laravel avec Vue.js.

Sommaire

  1. Prérequis techniques
  2. Comprendre Wayfinder et la génération des routes
  3. Concepts clés du composant Form
  4. Création d'un formulaire
  5. Modification avec formulaire
  6. Propriétés et options essentielles
  7. Bonnes pratiques
  8. Conclusion

1. Prérequis techniques

Backend Laravel

Frontend Vue.js

Route Laravel

1Route::middleware(['auth', 'verified'])->group(function (): void {
2 Route::resource('students', StudentController::class);
3});
1Route::middleware(['auth', 'verified'])->group(function (): void {
2 Route::resource('students', StudentController::class);
3});

2. Comprendre Wayfinder et la génération des routes

Qu'est-ce que Wayfinder?

Wayfinder est un package Laravel officiel qui génère automatiquement des helpers TypeScript pour vos routes Laravel côté frontend. Il élimine les URL codées en dur et garantit la synchronisation parfaite entre votre backend et frontend.

Génération des routes

Wayfinder analyse vos routes Laravel et crée des helpers JavaScript/TypeScript:

1// Route Laravel
2Route::resource('students', StudentController::class);
1// Route Laravel
2Route::resource('students', StudentController::class);

Génère côté frontend:

1// resources/js/routes/students/index.ts
2export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
3 url: index.url(options),
4 method: 'get',
5})
6 
7index.definition = {
8 methods: ["get","head"],
9 url: '/students',
10} satisfies RouteDefinition<["get","head"]>
11 
12 
13 
14export const create = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
15 url: create.url(options),
16 method: 'get',
17})
18 
19create.definition = {
20 methods: ["get","head"],
21 url: '/students/create',
22} satisfies RouteDefinition<["get","head"]>
23.
24.
25.
26 
27export const destroy = (args: { student: number | { id: number } } | [student: number | { id: number } ] | number | { id: number }, options?: RouteQueryOptions): RouteDefinition<'delete'> => ({
28 url: destroy.url(args, options),
29 method: 'delete',
30})
31 
32destroy.definition = {
33 methods: ["delete"],
34 url: '/students/{student}',
35}
1// resources/js/routes/students/index.ts
2export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
3 url: index.url(options),
4 method: 'get',
5})
6 
7index.definition = {
8 methods: ["get","head"],
9 url: '/students',
10} satisfies RouteDefinition<["get","head"]>
11 
12 
13 
14export const create = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
15 url: create.url(options),
16 method: 'get',
17})
18 
19create.definition = {
20 methods: ["get","head"],
21 url: '/students/create',
22} satisfies RouteDefinition<["get","head"]>
23.
24.
25.
26 
27export const destroy = (args: { student: number | { id: number } } | [student: number | { id: number } ] | number | { id: number }, options?: RouteQueryOptions): RouteDefinition<'delete'> => ({
28 url: destroy.url(args, options),
29 method: 'delete',
30})
31 
32destroy.definition = {
33 methods: ["delete"],
34 url: '/students/{student}',
35}

Intégration avec le composant Form

Au lieu d'écrire manuellement:

1<Form action="/students" method="post">
1<Form action="/students" method="post">

Vous utilisez Wayfinder avec le pattern de liaison dynamique:

1<Form v-bind="StudentController.store.form()">
2 ou
3 <Form :action="studentsRoutes.store.form().url">
1<Form v-bind="StudentController.store.form()">
2 ou
3 <Form :action="studentsRoutes.store.form().url">

Cette syntaxe se décompose ainsi:

  1. StudentController.store correspond à la méthode store de votre contrôleur
  2. .form() retourne un objet contenant { action: '/students', method: 'POST' }
  3. v-bind distribue ces propriétés au composant Form
  4. :action définit dynamiquement l'URL de soumission du formulaire

Avantages de Wayfinder

Type Safety: TypeScript détecte les erreurs de routes à la compilation.

1// ✅ Correct
2StudentController.store.form()
3 
4// ❌ Erreur TypeScript
5StudentController.stor.form() // Typo détectée
1// ✅ Correct
2StudentController.store.form()
3 
4// ❌ Erreur TypeScript
5StudentController.stor.form() // Typo détectée

Paramètres dynamiques: Pour les routes avec paramètres:

1<!-- Pour create/store: pas de paramètre -->
2<Form v-bind="StudentController.store.form()">
3 
4<!-- Pour update: ID requis -->
5<Form v-bind="StudentController.update.form(props.student.id)">
6 
7<!-- Pour delete: ID requis -->
8<Link v-bind="StudentController.destroy.link(student.id)" method="delete">
1<!-- Pour create/store: pas de paramètre -->
2<Form v-bind="StudentController.store.form()">
3 
4<!-- Pour update: ID requis -->
5<Form v-bind="StudentController.update.form(props.student.id)">
6 
7<!-- Pour delete: ID requis -->
8<Link v-bind="StudentController.destroy.link(student.id)" method="delete">

Refactoring sécurisé: Si vous renommez une route Laravel, Wayfinder met à jour automatiquement les helpers et TypeScript vous alertera sur tous les usages obsolètes.

Différence entre action manuelle et Wayfinder

Approche manuelle (sans Wayfinder):

1<template>
2 <!-- URL codée en dur, risque d'erreur -->
3 <Form action="/students" method="post">
4 <!-- Champs -->
5 </Form>
6 
7 <!-- Pour update, vous devez construire l'URL manuellement -->
8 <Form :action="`/students/${student.id}`" method="put">
9 <!-- Champs -->
10 </Form>
11</template>
1<template>
2 <!-- URL codée en dur, risque d'erreur -->
3 <Form action="/students" method="post">
4 <!-- Champs -->
5 </Form>
6 
7 <!-- Pour update, vous devez construire l'URL manuellement -->
8 <Form :action="`/students/${student.id}`" method="put">
9 <!-- Champs -->
10 </Form>
11</template>

Problèmes:

  • URL codées en dur
  • Pas de vérification TypeScript
  • Erreurs difficiles à détecter
  • Maintenance complexe lors de changements de routes

Approche Wayfinder (recommandée):

1<script setup lang="ts">
2import StudentController from '@/actions/App/Http/Controllers/StudentController';
3import type { Student } from '@/types/student';
4 
5interface Props {
6 student?: Student;
7}
8const props = defineProps<Props>();
9</script>
10 
11<template>
12 <!-- Pour création -->
13 <Form v-bind="StudentController.store.form()">
14 <!-- Champs -->
15 </Form>
16 
17 <!-- Pour mise à jour -->
18 <Form v-bind="StudentController.update.form(props.student.id)">
19 <!-- Champs -->
20 </Form>
21</template>
1<script setup lang="ts">
2import StudentController from '@/actions/App/Http/Controllers/StudentController';
3import type { Student } from '@/types/student';
4 
5interface Props {
6 student?: Student;
7}
8const props = defineProps<Props>();
9</script>
10 
11<template>
12 <!-- Pour création -->
13 <Form v-bind="StudentController.store.form()">
14 <!-- Champs -->
15 </Form>
16 
17 <!-- Pour mise à jour -->
18 <Form v-bind="StudentController.update.form(props.student.id)">
19 <!-- Champs -->
20 </Form>
21</template>

Avantages:

  • Type safety complet
  • Auto-complétion dans l'IDE
  • Détection des erreurs à la compilation
  • Maintenance simplifiée

Structure des fichiers générés

1resources/js/
2├── actions/
3│ └── App/
4│ └── Http/
5│ └── Controllers/
6│ └── StudentController.ts
7├── routes/
8│ └── students/
9│ └── index.ts
10└── types/
11 └── student.ts
1resources/js/
2├── actions/
3│ └── App/
4│ └── Http/
5│ └── Controllers/
6│ └── StudentController.ts
7├── routes/
8│ └── students/
9│ └── index.ts
10└── types/
11 └── student.ts

Le fichier StudentController.ts expose des méthodes correspondant aux actions de votre contrôleur:

1 
2export const store = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
3 url: store.url(options),
4 method: 'post',
5})
6 
7store.definition = {
8 methods: ["post"],
9 url: '/students',
10} satisfies RouteDefinition<["post"]>
11 
12store.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
13 url: store.url(options),
14 method: 'post',
15})
16.
17.
18.
1 
2export const store = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
3 url: store.url(options),
4 method: 'post',
5})
6 
7store.definition = {
8 methods: ["post"],
9 url: '/students',
10} satisfies RouteDefinition<["post"]>
11 
12store.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
13 url: store.url(options),
14 method: 'post',
15})
16.
17.
18.

3. Concepts clés du composant Form

Structure de base

Le composant nécessite deux attributs essentiels: action et method.

1<Form action="/users" method="post">
2 <input type="text" name="name" />
3 <button type="submit">Créer</button>
4</Form>
1<Form action="/users" method="post">
2 <input type="text" name="name" />
3 <button type="submit">Créer</button>
4</Form>

Point clé: Pas besoin de v-model, juste l'attribut name suffit. Le composant collecte automatiquement les valeurs.

Accès à l'état via slot props

1<Form action="/users" method="post" #default="{ errors, processing, wasSuccessful }">
2 <input type="text" name="name" />
3 <span v-if="errors.name">{{ errors.name }}</span>
4 
5 <button type="submit" :disabled="processing">
6 {{ processing ? 'Création...' : 'Créer' }}
7 </button>
8</Form>
1<Form action="/users" method="post" #default="{ errors, processing, wasSuccessful }">
2 <input type="text" name="name" />
3 <span v-if="errors.name">{{ errors.name }}</span>
4 
5 <button type="submit" :disabled="processing">
6 {{ processing ? 'Création...' : 'Créer' }}
7 </button>
8</Form>

Propriétés disponibles:

  • errors: Erreurs de validation
  • processing: État de soumission
  • wasSuccessful: Succès de la dernière soumission
  • isDirty: Formulaire modifié
  • setError, clearErrors: Manipulation des erreurs

Support des structures imbriquées

1<Form action="/users" method="post">
2 <input type="text" name="user.name" />
3 <input type="text" name="address.street" />
4 <input type="text" name="tags[]" />
5</Form>
1<Form action="/users" method="post">
2 <input type="text" name="user.name" />
3 <input type="text" name="address.street" />
4 <input type="text" name="tags[]" />
5</Form>

Génère automatiquement:

1{
2 "user": { "name": "John" },
3 "address": { "street": "123 Main" },
4 "tags": ["tag1"]
5}
1{
2 "user": { "name": "John" },
3 "address": { "street": "123 Main" },
4 "tags": ["tag1"]
5}

4. Création d'un formulaire

Contrôleur Laravel

1public function store(StudentRequest $request)
2{
3 $validated = $request->validated();
4 $validated['profile'] = $this->handleProfileUpload($request);
5 
6 Student::create($validated);
7 
8 return redirect()->route('students.index')
9 ->with('success', 'Étudiant créé avec succès.');
10}
1public function store(StudentRequest $request)
2{
3 $validated = $request->validated();
4 $validated['profile'] = $this->handleProfileUpload($request);
5 
6 Student::create($validated);
7 
8 return redirect()->route('students.index')
9 ->with('success', 'Étudiant créé avec succès.');
10}

Form Request

1public function rules(): array
2{
3 return [
4 'name' => ['required', 'string', 'max:255'],
5 'email' => ['required', 'email', 'unique:students'],
6 'profile' => ['nullable', 'image', 'max:2048'],
7 'bio' => ['nullable', 'string', 'max:500'],
8 ];
9}
1public function rules(): array
2{
3 return [
4 'name' => ['required', 'string', 'max:255'],
5 'email' => ['required', 'email', 'unique:students'],
6 'profile' => ['nullable', 'image', 'max:2048'],
7 'bio' => ['nullable', 'string', 'max:500'],
8 ];
9}

Composant Vue.js

1<script setup lang="ts">
2import { Form, Head, Link } from '@inertiajs/vue3';
3import StudentController from '@/actions/App/Http/Controllers/StudentController';
4import * as studentsRoute from '@/routes/students';
5import type { Student } from '@/types/student';
6 
7interface Props {
8 student?: Student | null;
9}
10defineProps<Props>();
11</script>
12 
13<template>
14 <Head title="Créer un étudiant" />
15 
16 <Form
17 v-bind="StudentController.store.form()"
18 enctype="multipart/form-data"
19 v-slot="{ errors, processing }"
20 >
21 <div class="form-group">
22 <label for="name">Nom complet</label>
23 <input
24 id="name"
25 name="name"
26 type="text"
27 />
28 <span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
29 </div>
30 
31 <div class="form-group">
32 <label for="email">Email</label>
33 <input
34 id="email"
35 name="email"
36 type="email"
37 />
38 <span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
39 </div>
40 
41 <div class="form-group">
42 <label for="profile">Photo de profil</label>
43 <input id="profile" name="profile" type="file" />
44 <span v-if="errors.profile" class="text-red-500">{{ errors.profile }}</span>
45 </div>
46 
47 <div class="form-group">
48 <label for="bio">Biographie</label>
49 <textarea id="bio" name="bio" :value="student?.bio"></textarea>
50 <span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
51 </div>
52 
53 <div class="form-actions">
54 <Link :href="studentsRoute.index().url" class="btn-secondary">
55 Retour
56 </Link>
57 <button type="submit" :disabled="processing" class="btn-primary">
58 {{ processing ? "Ajout en cours... ": "Ajouter un étudiant" }}
59 </button>
60 </div>
61 </Form>
62</template>
1<script setup lang="ts">
2import { Form, Head, Link } from '@inertiajs/vue3';
3import StudentController from '@/actions/App/Http/Controllers/StudentController';
4import * as studentsRoute from '@/routes/students';
5import type { Student } from '@/types/student';
6 
7interface Props {
8 student?: Student | null;
9}
10defineProps<Props>();
11</script>
12 
13<template>
14 <Head title="Créer un étudiant" />
15 
16 <Form
17 v-bind="StudentController.store.form()"
18 enctype="multipart/form-data"
19 v-slot="{ errors, processing }"
20 >
21 <div class="form-group">
22 <label for="name">Nom complet</label>
23 <input
24 id="name"
25 name="name"
26 type="text"
27 />
28 <span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
29 </div>
30 
31 <div class="form-group">
32 <label for="email">Email</label>
33 <input
34 id="email"
35 name="email"
36 type="email"
37 />
38 <span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
39 </div>
40 
41 <div class="form-group">
42 <label for="profile">Photo de profil</label>
43 <input id="profile" name="profile" type="file" />
44 <span v-if="errors.profile" class="text-red-500">{{ errors.profile }}</span>
45 </div>
46 
47 <div class="form-group">
48 <label for="bio">Biographie</label>
49 <textarea id="bio" name="bio" :value="student?.bio"></textarea>
50 <span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
51 </div>
52 
53 <div class="form-actions">
54 <Link :href="studentsRoute.index().url" class="btn-secondary">
55 Retour
56 </Link>
57 <button type="submit" :disabled="processing" class="btn-primary">
58 {{ processing ? "Ajout en cours... ": "Ajouter un étudiant" }}
59 </button>
60 </div>
61 </Form>
62</template>

Points clés:

  • v-bind="StudentController.store.form()": Génère automatiquement action et method via Wayfinder
  • enctype="multipart/form-data": Pour les uploads de fichiers
  • Pas de v-model
  • processing pour désactiver les boutons pendant la soumission

5. Modification avec formulaire

Contrôleur

1public function update(StudentRequest $request, Student $student)
2{
3 $validated = $request->validated();
4 $validated['profile'] = $this->handleProfileUpload($request, $student);
5 
6 $student->update($validated);
7 
8 return redirect()->route('students.index')
9 ->with('success', 'Étudiant mis à jour.');
10}
1public function update(StudentRequest $request, Student $student)
2{
3 $validated = $request->validated();
4 $validated['profile'] = $this->handleProfileUpload($request, $student);
5 
6 $student->update($validated);
7 
8 return redirect()->route('students.index')
9 ->with('success', 'Étudiant mis à jour.');
10}

Règles de validation avec ignore

1public function rules(): array
2{
3 $studentId = $this->route('student')?->id;
4 
5 return [
6 'email' => ['required', 'email', Rule::unique('students')->ignore($studentId)],
7 // Autres règles...
8 ];
9}
1public function rules(): array
2{
3 $studentId = $this->route('student')?->id;
4 
5 return [
6 'email' => ['required', 'email', Rule::unique('students')->ignore($studentId)],
7 // Autres règles...
8 ];
9}

Composant Vue.js

1<script setup lang="ts">
2import { Form, Head, Link } from '@inertiajs/vue3';
3import StudentController from '@/actions/App/Http/Controllers/StudentController';
4import type { Student } from '@/types/student';
5 
6interface Props {
7 student: Student;
8}
9 defineProps<Props>();
10</script>
11 
12<template>
13 <Head title="Modifier un étudiant" />
14 
15 <Form
16 v-bind="StudentController.update.form(props.student.id)"
17 enctype="multipart/form-data"
18 v-slot="{ errors, processing }"
19 >
20 <div class="form-group">
21 <label for="name">Nom complet</label>
22 <input id="name" name="name" type="text" :value="student.name" />
23 <span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
24 </div>
25 
26 <div class="form-group">
27 <label for="email">Email</label>
28 <input id="email" name="email" type="email" :value="student.email" />
29 <span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
30 </div>
31 
32 <div class="form-group">
33 <label for="bio">Biographie</label>
34 <textarea id="bio" name="bio" :value="student.bio"></textarea>
35 <span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
36 </div>
37 
38 <div class="form-actions">
39 <button type="submit" :disabled="processing" class="btn-primary">
40 <span v-if="processing">Enregistrement...</span>
41 <span v-else>Mettre à jour</span>
42 </button>
43 </div>
44 </Form>
45</template>
1<script setup lang="ts">
2import { Form, Head, Link } from '@inertiajs/vue3';
3import StudentController from '@/actions/App/Http/Controllers/StudentController';
4import type { Student } from '@/types/student';
5 
6interface Props {
7 student: Student;
8}
9 defineProps<Props>();
10</script>
11 
12<template>
13 <Head title="Modifier un étudiant" />
14 
15 <Form
16 v-bind="StudentController.update.form(props.student.id)"
17 enctype="multipart/form-data"
18 v-slot="{ errors, processing }"
19 >
20 <div class="form-group">
21 <label for="name">Nom complet</label>
22 <input id="name" name="name" type="text" :value="student.name" />
23 <span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
24 </div>
25 
26 <div class="form-group">
27 <label for="email">Email</label>
28 <input id="email" name="email" type="email" :value="student.email" />
29 <span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
30 </div>
31 
32 <div class="form-group">
33 <label for="bio">Biographie</label>
34 <textarea id="bio" name="bio" :value="student.bio"></textarea>
35 <span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
36 </div>
37 
38 <div class="form-actions">
39 <button type="submit" :disabled="processing" class="btn-primary">
40 <span v-if="processing">Enregistrement...</span>
41 <span v-else>Mettre à jour</span>
42 </button>
43 </div>
44 </Form>
45</template>

Différences avec la création:

  • StudentController.update.form(props.student.id): Route dynamique avec ID
  • Méthode PUT et PATCH générée automatiquement par Wayfinder
  • Valeurs pré-remplies depuis props.student

6. Propriétés et options essentielles

Transform: Modifier les données avant soumission

La propriété transform vous permet de manipuler les données du formulaire juste avant leur envoi au serveur. Cette fonction intercepte les données collectées et retourne l'objet final qui sera soumis.

Cas d'usage courants:

  • Ajouter des champs cachés ou calculés
  • Formatter des dates ou des nombres
  • Nettoyer ou transformer des valeurs
  • Ajouter des métadonnées (timestamps, user_id, etc.)
1<Form
2 action="/posts"
3 method="post"
4 :transform="(data) => ({
5 ...data,
6 user_id: currentUser.id,
7 timestamp: Date.now(),
8 slug: data.title.toLowerCase().replace(/\s+/g, '-')
9 })"
10>
11 <input type="text" name="title" />
12 <textarea name="content"></textarea>
13 <button type="submit">Publier</button>
14</Form>
1<Form
2 action="/posts"
3 method="post"
4 :transform="(data) => ({
5 ...data,
6 user_id: currentUser.id,
7 timestamp: Date.now(),
8 slug: data.title.toLowerCase().replace(/\s+/g, '-')
9 })"
10>
11 <input type="text" name="title" />
12 <textarea name="content"></textarea>
13 <button type="submit">Publier</button>
14</Form>

Exemple avec formatage de données:

1<script setup>
2const transformStudentData = (data) => {
3 return {
4 ...data,
5 // Convertir le tableau en JSON si nécessaire
6 programs: JSON.stringify(data.programs),
7 // Formatter la date de naissance
8 birth_date: data.birth_date ? new Date(data.birth_date).toISOString() : null,
9 // Nettoyer les espaces
10 name: data.name.trim(),
11 email: data.email.toLowerCase().trim()
12 };
13};
14</script>
15 
16<template>
17 <Form action="/students" method="post" :transform="transformStudentData">
18 <!-- Champs du formulaire -->
19 </Form>
20</template>
1<script setup>
2const transformStudentData = (data) => {
3 return {
4 ...data,
5 // Convertir le tableau en JSON si nécessaire
6 programs: JSON.stringify(data.programs),
7 // Formatter la date de naissance
8 birth_date: data.birth_date ? new Date(data.birth_date).toISOString() : null,
9 // Nettoyer les espaces
10 name: data.name.trim(),
11 email: data.email.toLowerCase().trim()
12 };
13};
14</script>
15 
16<template>
17 <Form action="/students" method="post" :transform="transformStudentData">
18 <!-- Champs du formulaire -->
19 </Form>
20</template>

Options de visite

Les options de visite contrôlent le comportement d'Inertia lors de la soumission du formulaire. Elles sont regroupées sous la propriété options pour éviter la confusion entre les propriétés de soumission et celles de rechargement.

1<Form
2 action="/profile"
3 method="put"
4 :options="{
5 preserveScroll: true,
6 preserveState: true,
7 only: ['user', 'flash']
8 }"
9>
1<Form
2 action="/profile"
3 method="put"
4 :options="{
5 preserveScroll: true,
6 preserveState: true,
7 only: ['user', 'flash']
8 }"
9>

Explication des options principales:

preserveScroll (boolean): Maintient la position de défilement de la page après la soumission. Très utile pour les formulaires situés en milieu de page.

1<!-- Idéal pour un formulaire de commentaire au milieu d'une longue page -->
2<Form
3 action="/comments"
4 method="post"
5 :options="{ preserveScroll: true }"
6>
1<!-- Idéal pour un formulaire de commentaire au milieu d'une longue page -->
2<Form
3 action="/comments"
4 method="post"
5 :options="{ preserveScroll: true }"
6>

preserveState (boolean): Conserve l'état local des composants qui ne sont pas rechargés. Empêche la perte de données dans d'autres parties de votre interface.

1<!-- Garde l'état d'un formulaire de recherche ouvert -->
2<Form
3 action="/profile/update"
4 method="put"
5 :options="{ preserveState: true }"
6>
1<!-- Garde l'état d'un formulaire de recherche ouvert -->
2<Form
3 action="/profile/update"
4 method="put"
5 :options="{ preserveState: true }"
6>

preserveUrl (boolean): Empêche la modification de l'URL du navigateur après la soumission. Utile pour les formulaires modaux ou les actions qui ne devraient pas changer l'URL.

1<Form
2 action="/newsletter/subscribe"
3 method="post"
4 :options="{ preserveUrl: true }"
5>
1<Form
2 action="/newsletter/subscribe"
3 method="post"
4 :options="{ preserveUrl: true }"
5>

replace (boolean): Remplace l'entrée actuelle de l'historique au lieu d'en créer une nouvelle. Évite que l'utilisateur ne revienne sur des états intermédiaires.

1<Form
2 action="/wizard/step-2"
3 method="post"
4 :options="{ replace: true }"
5>
1<Form
2 action="/wizard/step-2"
3 method="post"
4 :options="{ replace: true }"
5>

only (array): Ne recharge que les propriétés spécifiées depuis le serveur. Optimise les performances en réduisant la taille de la réponse.

1<!-- Ne recharge que le profil utilisateur et les messages flash -->
2<Form
3 action="/profile"
4 method="put"
5 :options="{ only: ['user', 'flash'] }"
6>
1<!-- Ne recharge que le profil utilisateur et les messages flash -->
2<Form
3 action="/profile"
4 method="put"
5 :options="{ only: ['user', 'flash'] }"
6>

except (array): Exclut certaines propriétés du rechargement. Utile pour éviter de recharger des données volumineuses non affectées.

1<!-- Ne pas recharger la liste des produits lors de la mise à jour du profil -->
2<Form
3 action="/profile"
4 method="put"
5 :options="{ except: ['products', 'categories'] }"
6>
1<!-- Ne pas recharger la liste des produits lors de la mise à jour du profil -->
2<Form
3 action="/profile"
4 method="put"
5 :options="{ except: ['products', 'categories'] }"
6>

reset (array): Réinitialise des propriétés spécifiques à leurs valeurs par défaut après la visite.

1<Form
2 action="/search"
3 method="get"
4 :options="{ reset: ['page', 'filters'] }"
5>
1<Form
2 action="/search"
3 method="get"
4 :options="{ reset: ['page', 'filters'] }"
5>

Exemple combiné complet:

1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3import type { User } from '@/types';
4 
5interface Props {
6 user: User;
7}
8const props = defineProps<Props>();
9</script>
10 
11<template>
12 <Form
13 v-bind="ProfileController.update.form(props.user.id)"
14 :options="{
15 preserveScroll: true, // Garde la position
16 preserveState: true, // Conserve l'état des autres composants
17 only: ['user', 'flash'], // Ne recharge que user et flash
18 onSuccess: () => {
19 // Callback après succès
20 console.log('Profil mis à jour');
21 }
22 }"
23 >
24 <!-- Champs du formulaire -->
25 </Form>
26</template>
1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3import type { User } from '@/types';
4 
5interface Props {
6 user: User;
7}
8const props = defineProps<Props>();
9</script>
10 
11<template>
12 <Form
13 v-bind="ProfileController.update.form(props.user.id)"
14 :options="{
15 preserveScroll: true, // Garde la position
16 preserveState: true, // Conserve l'état des autres composants
17 only: ['user', 'flash'], // Ne recharge que user et flash
18 onSuccess: () => {
19 // Callback après succès
20 console.log('Profil mis à jour');
21 }
22 }"
23 >
24 <!-- Champs du formulaire -->
25 </Form>
26</template>

Réinitialisation automatique

Les propriétés de réinitialisation automatisent le nettoyage des formulaires après soumission, améliorant l'expérience utilisateur.

resetOnSuccess: Réinitialise le formulaire après une soumission réussie.

1<!-- Réinitialiser tous les champs - Idéal pour formulaires de création -->
2<Form action="/users" method="post" resetOnSuccess>
3 <input type="text" name="name" />
4 <input type="email" name="email" />
5 <button type="submit">Créer</button>
6</Form>
1<!-- Réinitialiser tous les champs - Idéal pour formulaires de création -->
2<Form action="/users" method="post" resetOnSuccess>
3 <input type="text" name="name" />
4 <input type="email" name="email" />
5 <button type="submit">Créer</button>
6</Form>

Réinitialisation sélective: Ne réinitialise que certains champs spécifiques.

1<!-- Réinitialiser uniquement le mot de passe après soumission -->
2<Form action="/profile" method="put" :resetOnSuccess="['password', 'password_confirmation']">
3 <input type="text" name="name" value="John Doe" />
4 <input type="password" name="password" />
5 <input type="password" name="password_confirmation" />
6 <button type="submit">Mettre à jour</button>
7</Form>
1<!-- Réinitialiser uniquement le mot de passe après soumission -->
2<Form action="/profile" method="put" :resetOnSuccess="['password', 'password_confirmation']">
3 <input type="text" name="name" value="John Doe" />
4 <input type="password" name="password" />
5 <input type="password" name="password_confirmation" />
6 <button type="submit">Mettre à jour</button>
7</Form>

resetOnError: Réinitialise le formulaire en cas d'erreur. Moins courant mais utile pour certains cas.

1<!-- Réinitialiser tout en cas d'erreur -->
2<Form action="/payment" method="post" resetOnError>
3 <input type="text" name="card_number" />
4 <button type="submit">Payer</button>
5</Form>
6 
7<!-- Réinitialiser des champs sensibles uniquement -->
8<Form action="/payment" method="post" :resetOnError="['cvv', 'pin']">
9 <input type="text" name="card_number" />
10 <input type="text" name="cvv" />
11 <button type="submit">Payer</button>
12</Form>
1<!-- Réinitialiser tout en cas d'erreur -->
2<Form action="/payment" method="post" resetOnError>
3 <input type="text" name="card_number" />
4 <button type="submit">Payer</button>
5</Form>
6 
7<!-- Réinitialiser des champs sensibles uniquement -->
8<Form action="/payment" method="post" :resetOnError="['cvv', 'pin']">
9 <input type="text" name="card_number" />
10 <input type="text" name="cvv" />
11 <button type="submit">Payer</button>
12</Form>

setDefaultsOnSuccess: Définit les valeurs actuelles comme nouvelles valeurs par défaut après succès. Très utile pour les formulaires d'édition.

1<Form action="/profile" method="put" setDefaultsOnSuccess>
2 <input type="text" name="name" :value="user.name" />
3 <input type="email" name="email" :value="user.email" />
4 <button type="submit">Sauvegarder</button>
5</Form>
1<Form action="/profile" method="put" setDefaultsOnSuccess>
2 <input type="text" name="name" :value="user.name" />
3 <input type="email" name="email" :value="user.email" />
4 <button type="submit">Sauvegarder</button>
5</Form>

Pourquoi c'est important: Sans setDefaultsOnSuccess, après avoir sauvegardé un formulaire d'édition, le formulaire serait marqué comme "modifié" (isDirty: true) même sans changement, car les valeurs par défaut seraient toujours les anciennes. Cette propriété synchronise les valeurs par défaut avec les nouvelles valeurs sauvegardées.

Exemple pratique combiné:

1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3 
4const handleSuccess = () => {
5 // Afficher une notification
6 toast.success('Profil mis à jour avec succès');
7};
8</script>
9 
10<template>
11 <!-- Formulaire d'édition avec mise à jour des valeurs par défaut -->
12 <Form
13 action="/profile"
14 method="put"
15 setDefaultsOnSuccess
16 @success="handleSuccess"
17 >
18 <input type="text" name="name" :value="user.name" />
19 <button type="submit">Sauvegarder</button>
20 </Form>
21 
22 <!-- Formulaire de création avec réinitialisation complète -->
23 <Form
24 action="/posts"
25 method="post"
26 resetOnSuccess
27 @success="handleSuccess"
28 >
29 <input type="text" name="title" />
30 <textarea name="content"></textarea>
31 <button type="submit">Publier</button>
32 </Form>
33 
34 <!-- Formulaire sensible avec réinitialisation sélective -->
35 <Form
36 action="/password/update"
37 method="put"
38 :resetOnSuccess="['current_password', 'password', 'password_confirmation']"
39 >
40 <input type="password" name="current_password" />
41 <input type="password" name="password" />
42 <input type="password" name="password_confirmation" />
43 <button type="submit">Changer le mot de passe</button>
44 </Form>
45</template>
1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3 
4const handleSuccess = () => {
5 // Afficher une notification
6 toast.success('Profil mis à jour avec succès');
7};
8</script>
9 
10<template>
11 <!-- Formulaire d'édition avec mise à jour des valeurs par défaut -->
12 <Form
13 action="/profile"
14 method="put"
15 setDefaultsOnSuccess
16 @success="handleSuccess"
17 >
18 <input type="text" name="name" :value="user.name" />
19 <button type="submit">Sauvegarder</button>
20 </Form>
21 
22 <!-- Formulaire de création avec réinitialisation complète -->
23 <Form
24 action="/posts"
25 method="post"
26 resetOnSuccess
27 @success="handleSuccess"
28 >
29 <input type="text" name="title" />
30 <textarea name="content"></textarea>
31 <button type="submit">Publier</button>
32 </Form>
33 
34 <!-- Formulaire sensible avec réinitialisation sélective -->
35 <Form
36 action="/password/update"
37 method="put"
38 :resetOnSuccess="['current_password', 'password', 'password_confirmation']"
39 >
40 <input type="password" name="current_password" />
41 <input type="password" name="password" />
42 <input type="password" name="password_confirmation" />
43 <button type="submit">Changer le mot de passe</button>
44 </Form>
45</template>

Désactivation pendant traitement

La propriété disable-while-processing ajoute automatiquement l'attribut HTML inert au formulaire pendant sa soumission. Cet attribut natif du navigateur désactive toutes les interactions avec le formulaire.

1<Form
2 action="/users"
3 method="post"
4 disable-while-processing
5 class="inert:opacity-50 inert:pointer-events-none"
6>
7 <input type="text" name="name" />
8 <button type="submit">Créer</button>
9</Form>
1<Form
2 action="/users"
3 method="post"
4 disable-while-processing
5 class="inert:opacity-50 inert:pointer-events-none"
6>
7 <input type="text" name="name" />
8 <button type="submit">Créer</button>
9</Form>

Comment ça fonctionne:

  1. L'utilisateur clique sur "Soumettre"
  2. Le formulaire reçoit l'attribut inert
  3. Tous les champs et boutons deviennent non interactifs
  4. Les styles CSS s'appliquent automatiquement via le sélecteur :inert
  5. Après la réponse du serveur, l'attribut inert est retiré

Styles CSS/Tailwind pour l'état inert:

1 
2<Form
3 disable-while-processing
4 class="
5 inert:opacity-50
6 inert:pointer-events-none
7 inert:cursor-not-allowed
8 transition-opacity duration-200
9 "
10>
1 
2<Form
3 disable-while-processing
4 class="
5 inert:opacity-50
6 inert:pointer-events-none
7 inert:cursor-not-allowed
8 transition-opacity duration-200
9 "
10>

Avantages:

  • Empêche les doubles soumissions
  • Feedback visuel immédiat pour l'utilisateur
  • Aucun JavaScript manuel nécessaire
  • Compatible avec tous les éléments du formulaire

Exemple avec indicateur de chargement personnalisé:

1<script setup>
2import { ref } from 'vue';
3</script>
4 
5<template>
6 <Form
7 action="/users"
8 method="post"
9 disable-while-processing
10 class="relative inert:opacity-70"
11 v-slot="{ processing }"
12 >
13 <!-- Overlay de chargement -->
14 <div
15 v-if="processing"
16 class="absolute inset-0 flex items-center justify-center bg-white/80 z-10"
17 >
18 <svg class="animate-spin h-8 w-8 text-blue-500" viewBox="0 0 24 24">
19 <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
20 <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
21 </svg>
22 </div>
23 
24 <input type="text" name="name" />
25 <button type="submit" :disabled="processing">
26 {{ processing ? 'Création...' : 'Créer' }}
27 </button>
28 </Form>
29</template>
1<script setup>
2import { ref } from 'vue';
3</script>
4 
5<template>
6 <Form
7 action="/users"
8 method="post"
9 disable-while-processing
10 class="relative inert:opacity-70"
11 v-slot="{ processing }"
12 >
13 <!-- Overlay de chargement -->
14 <div
15 v-if="processing"
16 class="absolute inset-0 flex items-center justify-center bg-white/80 z-10"
17 >
18 <svg class="animate-spin h-8 w-8 text-blue-500" viewBox="0 0 24 24">
19 <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
20 <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
21 </svg>
22 </div>
23 
24 <input type="text" name="name" />
25 <button type="submit" :disabled="processing">
26 {{ processing ? 'Création...' : 'Créer' }}
27 </button>
28 </Form>
29</template>

Événements

Le composant <Form> émet tous les événements du cycle de vie d'une visite Inertia. Ces événements permettent d'exécuter du code à différentes étapes de la soumission.

1<Form
2 action="/users"
3 method="post"
4 @before="handleBefore"
5 @start="handleStart"
6 @progress="handleProgress"
7 @success="handleSuccess"
8 @error="handleError"
9 @finish="handleFinish"
10 @cancel="handleCancel"
11>
1<Form
2 action="/users"
3 method="post"
4 @before="handleBefore"
5 @start="handleStart"
6 @progress="handleProgress"
7 @success="handleSuccess"
8 @error="handleError"
9 @finish="handleFinish"
10 @cancel="handleCancel"
11>

Description détaillée de chaque événement:

@before: Se déclenche avant l'envoi de la requête. Retourner false annule la soumission.

1<script setup>
2const handleBefore = (visit) => {
3 // Validation personnalisée avant soumission
4 if (!confirm('Êtes-vous sûr de vouloir continuer ?')) {
5 return false; // Annule la soumission
6 }
7 
8 // Logger l'action
9 console.log('Formulaire sur le point d\'être soumis', visit);
10};
11</script>
12 
13<template>
14 <Form action="/users" method="post" @before="handleBefore">
15 <input type="text" name="name" />
16 </Form>
17</template>
1<script setup>
2const handleBefore = (visit) => {
3 // Validation personnalisée avant soumission
4 if (!confirm('Êtes-vous sûr de vouloir continuer ?')) {
5 return false; // Annule la soumission
6 }
7 
8 // Logger l'action
9 console.log('Formulaire sur le point d\'être soumis', visit);
10};
11</script>
12 
13<template>
14 <Form action="/users" method="post" @before="handleBefore">
15 <input type="text" name="name" />
16 </Form>
17</template>

. . .

La suite voir la Docs


7. Bonnes pratiques

1. Composant wrapper centralisé

Créez un composant wrapper pour centraliser la configuration commune de vos formulaires. Cela évite la répétition et facilite la maintenance.

1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3 
4interface Props {
5 action: string;
6 method: string;
7 preserveScroll?: boolean;
8}
9 
10const props = withDefaults(defineProps<Props>(), {
11 preserveScroll: true
12});
13</script>
14 
15<template>
16 <Form
17 :action="props.action"
18 :method="props.method"
19 :options="{ preserveScroll: props.preserveScroll }"
20 disable-while-processing
21 class="inert:opacity-50 inert:pointer-events-none transition-opacity"
22 >
23 <slot />
24 </Form>
25</template>
1<script setup lang="ts">
2import { Form } from '@inertiajs/vue3';
3 
4interface Props {
5 action: string;
6 method: string;
7 preserveScroll?: boolean;
8}
9 
10const props = withDefaults(defineProps<Props>(), {
11 preserveScroll: true
12});
13</script>
14 
15<template>
16 <Form
17 :action="props.action"
18 :method="props.method"
19 :options="{ preserveScroll: props.preserveScroll }"
20 disable-while-processing
21 class="inert:opacity-50 inert:pointer-events-none transition-opacity"
22 >
23 <slot />
24 </Form>
25</template>

Utilisation:

1<template>
2 <AppForm action="/users" method="post">
3 <input type="text" name="name" />
4 <button type="submit">Créer</button>
5 </AppForm>
6</template>
1<template>
2 <AppForm action="/users" method="post">
3 <input type="text" name="name" />
4 <button type="submit">Créer</button>
5 </AppForm>
6</template>

Avantages:

  • Configuration DRY (Don't Repeat Yourself)
  • Styles et comportements cohérents dans toute l'application
  • Modifications centralisées faciles

2. Composant de champ réutilisable

Encapsulez vos champs de formulaire dans des composants réutilisables incluant labels, erreurs et styles.

1<script setup lang="ts">
2interface Props {
3 name: string;
4 label: string;
5 type?: string;
6 error?: string;
7 modelValue?: string;
8 placeholder?: string;
9 required?: boolean;
10}
11 
12const props = withDefaults(defineProps<Props>(), {
13 type: 'text',
14 required: false
15});
16</script>
17 
18<template>
19 <div class="form-group mb-4">
20 <label
21 :for="props.name"
22 class="block text-sm font-medium text-gray-700 mb-1"
23 >
24 {{ props.label }}
25 <span v-if="props.required" class="text-red-500">*</span>
26 </label>
27 
28 <input
29 :id="props.name"
30 :name="props.name"
31 :type="props.type"
32 :value="props.modelValue"
33 :placeholder="props.placeholder"
34 :required="props.required"
35 class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
36 :class="{ 'border-red-500': props.error }"
37 />
38 
39 <span v-if="props.error" class="text-red-500 text-sm mt-1 block">
40 {{ props.error }}
41 </span>
42 </div>
43</template>
1<script setup lang="ts">
2interface Props {
3 name: string;
4 label: string;
5 type?: string;
6 error?: string;
7 modelValue?: string;
8 placeholder?: string;
9 required?: boolean;
10}
11 
12const props = withDefaults(defineProps<Props>(), {
13 type: 'text',
14 required: false
15});
16</script>
17 
18<template>
19 <div class="form-group mb-4">
20 <label
21 :for="props.name"
22 class="block text-sm font-medium text-gray-700 mb-1"
23 >
24 {{ props.label }}
25 <span v-if="props.required" class="text-red-500">*</span>
26 </label>
27 
28 <input
29 :id="props.name"
30 :name="props.name"
31 :type="props.type"
32 :value="props.modelValue"
33 :placeholder="props.placeholder"
34 :required="props.required"
35 class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
36 :class="{ 'border-red-500': props.error }"
37 />
38 
39 <span v-if="props.error" class="text-red-500 text-sm mt-1 block">
40 {{ props.error }}
41 </span>
42 </div>
43</template>

Utilisation:

1<template>
2 <Form v-bind="StudentController.store.form()" v-slot="{ errors }">
3 <FormField
4 name="name"
5 label="Nom complet"
6 :error="errors.name"
7 :model-value="student?.name"
8 placeholder="ex: Jean Dupont"
9 required
10 />
11 
12 <FormField
13 name="email"
14 label="Email"
15 type="email"
16 :error="errors.email"
17 :model-value="student?.email"
18 required
19 />
20 
21 <button type="submit">Créer</button>
22 </Form>
23</template>
1<template>
2 <Form v-bind="StudentController.store.form()" v-slot="{ errors }">
3 <FormField
4 name="name"
5 label="Nom complet"
6 :error="errors.name"
7 :model-value="student?.name"
8 placeholder="ex: Jean Dupont"
9 required
10 />
11 
12 <FormField
13 name="email"
14 label="Email"
15 type="email"
16 :error="errors.email"
17 :model-value="student?.email"
18 required
19 />
20 
21 <button type="submit">Créer</button>
22 </Form>
23</template>

3. Validation avant soumission

Implémentez une validation côté client avant d'envoyer la requête au serveur pour améliorer l'expérience utilisateur.

1<script setup>
2import { ref } from 'vue';
3 
4const localErrors = ref({});
5 
6const handleBefore = (event) => {
7 localErrors.value = {};
8 
9 const formData = new FormData(event.target);
10 const email = formData.get('email');
11 const password = formData.get('password');
12 
13 // Validation email
14 if (!email || !email.includes('@')) {
15 localErrors.value.email = 'Format d\'email invalide';
16 }
17 
18 // Validation mot de passe
19 if (!password || password.length < 8) {
20 localErrors.value.password = 'Le mot de passe doit contenir au moins 8 caractères';
21 }
22 
23 // Annuler si erreurs
24 if (Object.keys(localErrors.value).length > 0) {
25 return false;
26 }
27 
28 return true;
29};
30</script>
31 
32<template>
33 <Form action="/users" method="post" @before="handleBefore" v-slot="{ errors }">
34 <div class="form-group">
35 <input type="email" name="email" />
36 <span v-if="localErrors.email || errors.email" class="text-red-500">
37 {{ localErrors.email || errors.email }}
38 </span>
39 </div>
40 
41 <div class="form-group">
42 <input type="password" name="password" />
43 <span v-if="localErrors.password || errors.password" class="text-red-500">
44 {{ localErrors.password || errors.password }}
45 </span>
46 </div>
47 
48 <button type="submit">Créer</button>
49 </Form>
50</template>
1<script setup>
2import { ref } from 'vue';
3 
4const localErrors = ref({});
5 
6const handleBefore = (event) => {
7 localErrors.value = {};
8 
9 const formData = new FormData(event.target);
10 const email = formData.get('email');
11 const password = formData.get('password');
12 
13 // Validation email
14 if (!email || !email.includes('@')) {
15 localErrors.value.email = 'Format d\'email invalide';
16 }
17 
18 // Validation mot de passe
19 if (!password || password.length < 8) {
20 localErrors.value.password = 'Le mot de passe doit contenir au moins 8 caractères';
21 }
22 
23 // Annuler si erreurs
24 if (Object.keys(localErrors.value).length > 0) {
25 return false;
26 }
27 
28 return true;
29};
30</script>
31 
32<template>
33 <Form action="/users" method="post" @before="handleBefore" v-slot="{ errors }">
34 <div class="form-group">
35 <input type="email" name="email" />
36 <span v-if="localErrors.email || errors.email" class="text-red-500">
37 {{ localErrors.email || errors.email }}
38 </span>
39 </div>
40 
41 <div class="form-group">
42 <input type="password" name="password" />
43 <span v-if="localErrors.password || errors.password" class="text-red-500">
44 {{ localErrors.password || errors.password }}
45 </span>
46 </div>
47 
48 <button type="submit">Créer</button>
49 </Form>
50</template>

Avantages:

  • Feedback immédiat sans attendre la réponse serveur
  • Réduit le nombre de requêtes inutiles
  • Meilleure expérience utilisateur

4. Accès programmatique

Utilisez les refs pour contrôler le formulaire depuis l'extérieur ou déclencher des actions personnalisées.

1<script setup>
2import { ref } from 'vue';
3import { Form } from '@inertiajs/vue3';
4 
5const formRef = ref();
6const autoSaveEnabled = ref(true);
7 
8// Soumission manuelle
9const submitForm = () => {
10 if (formRef.value) {
11 formRef.value.submit();
12 }
13};
14 
15// Réinitialisation manuelle
16const resetForm = () => {
17 if (formRef.value) {
18 formRef.value.reset();
19 }
20};
21 
22// Auto-save toutes les 30 secondes
23setInterval(() => {
24 if (autoSaveEnabled.value && formRef.value) {
25 formRef.value.submit();
26 }
27}, 30000);
28</script>
29 
30<template>
31 <div>
32 <Form ref="formRef" action="/draft/save" method="post">
33 <textarea name="content" rows="10"></textarea>
34 </Form>
35 
36 <div class="actions mt-4 space-x-2">
37 <button @click="submitForm" class="btn-primary">
38 Sauvegarder maintenant
39 </button>
40 
41 <button @click="resetForm" class="btn-secondary">
42 Réinitialiser
43 </button>
44 
45 <label class="inline-flex items-center">
46 <input
47 type="checkbox"
48 v-model="autoSaveEnabled"
49 class="mr-2"
50 />
51 Auto-save activé
52 </label>
53 </div>
54 </div>
55</template>
1<script setup>
2import { ref } from 'vue';
3import { Form } from '@inertiajs/vue3';
4 
5const formRef = ref();
6const autoSaveEnabled = ref(true);
7 
8// Soumission manuelle
9const submitForm = () => {
10 if (formRef.value) {
11 formRef.value.submit();
12 }
13};
14 
15// Réinitialisation manuelle
16const resetForm = () => {
17 if (formRef.value) {
18 formRef.value.reset();
19 }
20};
21 
22// Auto-save toutes les 30 secondes
23setInterval(() => {
24 if (autoSaveEnabled.value && formRef.value) {
25 formRef.value.submit();
26 }
27}, 30000);
28</script>
29 
30<template>
31 <div>
32 <Form ref="formRef" action="/draft/save" method="post">
33 <textarea name="content" rows="10"></textarea>
34 </Form>
35 
36 <div class="actions mt-4 space-x-2">
37 <button @click="submitForm" class="btn-primary">
38 Sauvegarder maintenant
39 </button>
40 
41 <button @click="resetForm" class="btn-secondary">
42 Réinitialiser
43 </button>
44 
45 <label class="inline-flex items-center">
46 <input
47 type="checkbox"
48 v-model="autoSaveEnabled"
49 class="mr-2"
50 />
51 Auto-save activé
52 </label>
53 </div>
54 </div>
55</template>

Cas d'usage:

  • Auto-save de brouillons
  • Soumission via raccourcis clavier
  • Validation personnalisée complexe
  • Intégration avec d'autres composants

Conclusion

Le composant <Form> d'Inertia.js simplifie drastiquement la gestion des formulaires en éliminant le code répétitif. Avec Wayfinder pour la génération automatique des routes et la gestion intégrée des erreurs de validation, vous pouvez construire des formulaires robustes et maintenables en quelques lignes de code. Adoptez ces patterns pour optimiser vos projets Laravel avec Vue.js.

Ressources officielles