19 min de lecture
11 vues

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

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

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

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:

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

Génère côté frontend:

// resources/js/routes/students/index.ts
export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
url: index.url(options),
method: 'get',
})
 
index.definition = {
methods: ["get","head"],
url: '/students',
} satisfies RouteDefinition<["get","head"]>
 
 
 
export const create = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
url: create.url(options),
method: 'get',
})
 
create.definition = {
methods: ["get","head"],
url: '/students/create',
} satisfies RouteDefinition<["get","head"]>
.
.
.
 
export const destroy = (args: { student: number | { id: number } } | [student: number | { id: number } ] | number | { id: number }, options?: RouteQueryOptions): RouteDefinition<'delete'> => ({
url: destroy.url(args, options),
method: 'delete',
})
 
destroy.definition = {
methods: ["delete"],
url: '/students/{student}',
}

Intégration avec le composant Form

Au lieu d'écrire manuellement:

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

Vous utilisez Wayfinder avec le pattern de liaison dynamique:

<Form v-bind="StudentController.store.form()">
ou
<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.

// ✅ Correct
StudentController.store.form()
 
// ❌ Erreur TypeScript
StudentController.stor.form() // Typo détectée

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

<!-- Pour create/store: pas de paramètre -->
<Form v-bind="StudentController.store.form()">
 
<!-- Pour update: ID requis -->
<Form v-bind="StudentController.update.form(props.student.id)">
 
<!-- Pour delete: ID requis -->
<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):

<template>
<!-- URL codée en dur, risque d'erreur -->
<Form action="/students" method="post">
<!-- Champs -->
</Form>
 
<!-- Pour update, vous devez construire l'URL manuellement -->
<Form :action="`/students/${student.id}`" method="put">
<!-- Champs -->
</Form>
</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):

<script setup lang="ts">
import StudentController from '@/actions/App/Http/Controllers/StudentController';
import type { Student } from '@/types/student';
 
interface Props {
student?: Student;
}
const props = defineProps<Props>();
</script>
 
<template>
<!-- Pour création -->
<Form v-bind="StudentController.store.form()">
<!-- Champs -->
</Form>
 
<!-- Pour mise à jour -->
<Form v-bind="StudentController.update.form(props.student.id)">
<!-- Champs -->
</Form>
</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

resources/js/
├── actions/
│ └── App/
│ └── Http/
│ └── Controllers/
│ └── StudentController.ts
├── routes/
│ └── students/
│ └── index.ts
└── types/
└── student.ts

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

 
export const store = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
url: store.url(options),
method: 'post',
})
 
store.definition = {
methods: ["post"],
url: '/students',
} satisfies RouteDefinition<["post"]>
 
store.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({
url: store.url(options),
method: 'post',
})
.
.
.

3. Concepts clés du composant Form

Structure de base

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

<Form action="/users" method="post">
<input type="text" name="name" />
<button type="submit">Créer</button>
</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

<Form action="/users" method="post" #default="{ errors, processing, wasSuccessful }">
<input type="text" name="name" />
<span v-if="errors.name">{{ errors.name }}</span>
 
<button type="submit" :disabled="processing">
{{ processing ? 'Création...' : 'Créer' }}
</button>
</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

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

Génère automatiquement:

{
"user": { "name": "John" },
"address": { "street": "123 Main" },
"tags": ["tag1"]
}

4. Création d'un formulaire

Contrôleur Laravel

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

Form Request

public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:students'],
'profile' => ['nullable', 'image', 'max:2048'],
'bio' => ['nullable', 'string', 'max:500'],
];
}

Composant Vue.js

<script setup lang="ts">
import { Form, Head, Link } from '@inertiajs/vue3';
import StudentController from '@/actions/App/Http/Controllers/StudentController';
import * as studentsRoute from '@/routes/students';
import type { Student } from '@/types/student';
 
interface Props {
student?: Student | null;
}
defineProps<Props>();
</script>
 
<template>
<Head title="Créer un étudiant" />
 
<Form
v-bind="StudentController.store.form()"
enctype="multipart/form-data"
v-slot="{ errors, processing }"
>
<div class="form-group">
<label for="name">Nom complet</label>
<input
id="name"
name="name"
type="text"
/>
<span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
</div>
 
<div class="form-group">
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
/>
<span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
</div>
 
<div class="form-group">
<label for="profile">Photo de profil</label>
<input id="profile" name="profile" type="file" />
<span v-if="errors.profile" class="text-red-500">{{ errors.profile }}</span>
</div>
 
<div class="form-group">
<label for="bio">Biographie</label>
<textarea id="bio" name="bio" :value="student?.bio"></textarea>
<span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
</div>
 
<div class="form-actions">
<Link :href="studentsRoute.index().url" class="btn-secondary">
Retour
</Link>
<button type="submit" :disabled="processing" class="btn-primary">
{{ processing ? "Ajout en cours... ": "Ajouter un étudiant" }}
</button>
</div>
</Form>
</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

public function update(StudentRequest $request, Student $student)
{
$validated = $request->validated();
$validated['profile'] = $this->handleProfileUpload($request, $student);
 
$student->update($validated);
 
return redirect()->route('students.index')
->with('success', 'Étudiant mis à jour.');
}

Règles de validation avec ignore

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

Composant Vue.js

<script setup lang="ts">
import { Form, Head, Link } from '@inertiajs/vue3';
import StudentController from '@/actions/App/Http/Controllers/StudentController';
import type { Student } from '@/types/student';
 
interface Props {
student: Student;
}
defineProps<Props>();
</script>
 
<template>
<Head title="Modifier un étudiant" />
 
<Form
v-bind="StudentController.update.form(props.student.id)"
enctype="multipart/form-data"
v-slot="{ errors, processing }"
>
<div class="form-group">
<label for="name">Nom complet</label>
<input id="name" name="name" type="text" :value="student.name" />
<span v-if="errors.name" class="text-red-500">{{ errors.name }}</span>
</div>
 
<div class="form-group">
<label for="email">Email</label>
<input id="email" name="email" type="email" :value="student.email" />
<span v-if="errors.email" class="text-red-500">{{ errors.email }}</span>
</div>
 
<div class="form-group">
<label for="bio">Biographie</label>
<textarea id="bio" name="bio" :value="student.bio"></textarea>
<span v-if="errors.bio" class="text-red-500">{{ errors.bio }}</span>
</div>
 
<div class="form-actions">
<button type="submit" :disabled="processing" class="btn-primary">
<span v-if="processing">Enregistrement...</span>
<span v-else>Mettre à jour</span>
</button>
</div>
</Form>
</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.)
<Form
action="/posts"
method="post"
:transform="(data) => ({
...data,
user_id: currentUser.id,
timestamp: Date.now(),
slug: data.title.toLowerCase().replace(/\s+/g, '-')
})"
>
<input type="text" name="title" />
<textarea name="content"></textarea>
<button type="submit">Publier</button>
</Form>

Exemple avec formatage de données:

<script setup>
const transformStudentData = (data) => {
return {
...data,
// Convertir le tableau en JSON si nécessaire
programs: JSON.stringify(data.programs),
// Formatter la date de naissance
birth_date: data.birth_date ? new Date(data.birth_date).toISOString() : null,
// Nettoyer les espaces
name: data.name.trim(),
email: data.email.toLowerCase().trim()
};
};
</script>
 
<template>
<Form action="/students" method="post" :transform="transformStudentData">
<!-- Champs du formulaire -->
</Form>
</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.

<Form
action="/profile"
method="put"
:options="{
preserveScroll: true,
preserveState: true,
only: ['user', 'flash']
}"
>

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.

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

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.

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

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.

<Form
action="/newsletter/subscribe"
method="post"
:options="{ preserveUrl: true }"
>

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.

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

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.

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

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

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

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

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

Exemple combiné complet:

<script setup lang="ts">
import { Form } from '@inertiajs/vue3';
import type { User } from '@/types';
 
interface Props {
user: User;
}
const props = defineProps<Props>();
</script>
 
<template>
<Form
v-bind="ProfileController.update.form(props.user.id)"
:options="{
preserveScroll: true, // Garde la position
preserveState: true, // Conserve l'état des autres composants
only: ['user', 'flash'], // Ne recharge que user et flash
onSuccess: () => {
// Callback après succès
console.log('Profil mis à jour');
}
}"
>
<!-- Champs du formulaire -->
</Form>
</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.

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

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

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

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

<!-- Réinitialiser tout en cas d'erreur -->
<Form action="/payment" method="post" resetOnError>
<input type="text" name="card_number" />
<button type="submit">Payer</button>
</Form>
 
<!-- Réinitialiser des champs sensibles uniquement -->
<Form action="/payment" method="post" :resetOnError="['cvv', 'pin']">
<input type="text" name="card_number" />
<input type="text" name="cvv" />
<button type="submit">Payer</button>
</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.

<Form action="/profile" method="put" setDefaultsOnSuccess>
<input type="text" name="name" :value="user.name" />
<input type="email" name="email" :value="user.email" />
<button type="submit">Sauvegarder</button>
</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é:

<script setup lang="ts">
import { Form } from '@inertiajs/vue3';
 
const handleSuccess = () => {
// Afficher une notification
toast.success('Profil mis à jour avec succès');
};
</script>
 
<template>
<!-- Formulaire d'édition avec mise à jour des valeurs par défaut -->
<Form
action="/profile"
method="put"
setDefaultsOnSuccess
@success="handleSuccess"
>
<input type="text" name="name" :value="user.name" />
<button type="submit">Sauvegarder</button>
</Form>
 
<!-- Formulaire de création avec réinitialisation complète -->
<Form
action="/posts"
method="post"
resetOnSuccess
@success="handleSuccess"
>
<input type="text" name="title" />
<textarea name="content"></textarea>
<button type="submit">Publier</button>
</Form>
 
<!-- Formulaire sensible avec réinitialisation sélective -->
<Form
action="/password/update"
method="put"
:resetOnSuccess="['current_password', 'password', 'password_confirmation']"
>
<input type="password" name="current_password" />
<input type="password" name="password" />
<input type="password" name="password_confirmation" />
<button type="submit">Changer le mot de passe</button>
</Form>
</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.

<Form
action="/users"
method="post"
disable-while-processing
class="inert:opacity-50 inert:pointer-events-none"
>
<input type="text" name="name" />
<button type="submit">Créer</button>
</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:

 
<Form
disable-while-processing
class="
inert:opacity-50
inert:pointer-events-none
inert:cursor-not-allowed
transition-opacity duration-200
"
>

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é:

<script setup>
import { ref } from 'vue';
</script>
 
<template>
<Form
action="/users"
method="post"
disable-while-processing
class="relative inert:opacity-70"
v-slot="{ processing }"
>
<!-- Overlay de chargement -->
<div
v-if="processing"
class="absolute inset-0 flex items-center justify-center bg-white/80 z-10"
>
<svg class="animate-spin h-8 w-8 text-blue-500" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
<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"/>
</svg>
</div>
 
<input type="text" name="name" />
<button type="submit" :disabled="processing">
{{ processing ? 'Création...' : 'Créer' }}
</button>
</Form>
</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.

<Form
action="/users"
method="post"
@before="handleBefore"
@start="handleStart"
@progress="handleProgress"
@success="handleSuccess"
@error="handleError"
@finish="handleFinish"
@cancel="handleCancel"
>

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

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

<script setup>
const handleBefore = (visit) => {
// Validation personnalisée avant soumission
if (!confirm('Êtes-vous sûr de vouloir continuer ?')) {
return false; // Annule la soumission
}
 
// Logger l'action
console.log('Formulaire sur le point d\'être soumis', visit);
};
</script>
 
<template>
<Form action="/users" method="post" @before="handleBefore">
<input type="text" name="name" />
</Form>
</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.

<script setup lang="ts">
import { Form } from '@inertiajs/vue3';
 
interface Props {
action: string;
method: string;
preserveScroll?: boolean;
}
 
const props = withDefaults(defineProps<Props>(), {
preserveScroll: true
});
</script>
 
<template>
<Form
:action="props.action"
:method="props.method"
:options="{ preserveScroll: props.preserveScroll }"
disable-while-processing
class="inert:opacity-50 inert:pointer-events-none transition-opacity"
>
<slot />
</Form>
</template>

Utilisation:

<template>
<AppForm action="/users" method="post">
<input type="text" name="name" />
<button type="submit">Créer</button>
</AppForm>
</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.

<script setup lang="ts">
interface Props {
name: string;
label: string;
type?: string;
error?: string;
modelValue?: string;
placeholder?: string;
required?: boolean;
}
 
const props = withDefaults(defineProps<Props>(), {
type: 'text',
required: false
});
</script>
 
<template>
<div class="form-group mb-4">
<label
:for="props.name"
class="block text-sm font-medium text-gray-700 mb-1"
>
{{ props.label }}
<span v-if="props.required" class="text-red-500">*</span>
</label>
 
<input
:id="props.name"
:name="props.name"
:type="props.type"
:value="props.modelValue"
:placeholder="props.placeholder"
:required="props.required"
class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
:class="{ 'border-red-500': props.error }"
/>
 
<span v-if="props.error" class="text-red-500 text-sm mt-1 block">
{{ props.error }}
</span>
</div>
</template>

Utilisation:

<template>
<Form v-bind="StudentController.store.form()" v-slot="{ errors }">
<FormField
name="name"
label="Nom complet"
:error="errors.name"
:model-value="student?.name"
placeholder="ex: Jean Dupont"
required
/>
 
<FormField
name="email"
label="Email"
type="email"
:error="errors.email"
:model-value="student?.email"
required
/>
 
<button type="submit">Créer</button>
</Form>
</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.

<script setup>
import { ref } from 'vue';
 
const localErrors = ref({});
 
const handleBefore = (event) => {
localErrors.value = {};
 
const formData = new FormData(event.target);
const email = formData.get('email');
const password = formData.get('password');
 
// Validation email
if (!email || !email.includes('@')) {
localErrors.value.email = 'Format d\'email invalide';
}
 
// Validation mot de passe
if (!password || password.length < 8) {
localErrors.value.password = 'Le mot de passe doit contenir au moins 8 caractères';
}
 
// Annuler si erreurs
if (Object.keys(localErrors.value).length > 0) {
return false;
}
 
return true;
};
</script>
 
<template>
<Form action="/users" method="post" @before="handleBefore" v-slot="{ errors }">
<div class="form-group">
<input type="email" name="email" />
<span v-if="localErrors.email || errors.email" class="text-red-500">
{{ localErrors.email || errors.email }}
</span>
</div>
 
<div class="form-group">
<input type="password" name="password" />
<span v-if="localErrors.password || errors.password" class="text-red-500">
{{ localErrors.password || errors.password }}
</span>
</div>
 
<button type="submit">Créer</button>
</Form>
</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.

<script setup>
import { ref } from 'vue';
import { Form } from '@inertiajs/vue3';
 
const formRef = ref();
const autoSaveEnabled = ref(true);
 
// Soumission manuelle
const submitForm = () => {
if (formRef.value) {
formRef.value.submit();
}
};
 
// Réinitialisation manuelle
const resetForm = () => {
if (formRef.value) {
formRef.value.reset();
}
};
 
// Auto-save toutes les 30 secondes
setInterval(() => {
if (autoSaveEnabled.value && formRef.value) {
formRef.value.submit();
}
}, 30000);
</script>
 
<template>
<div>
<Form ref="formRef" action="/draft/save" method="post">
<textarea name="content" rows="10"></textarea>
</Form>
 
<div class="actions mt-4 space-x-2">
<button @click="submitForm" class="btn-primary">
Sauvegarder maintenant
</button>
 
<button @click="resetForm" class="btn-secondary">
Réinitialiser
</button>
 
<label class="inline-flex items-center">
<input
type="checkbox"
v-model="autoSaveEnabled"
class="mr-2"
/>
Auto-save activé
</label>
</div>
</div>
</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