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
- Prérequis techniques
- Comprendre Wayfinder et la génération des routes
- Concepts clés du composant Form
- Création d'un formulaire
- Modification avec formulaire
- Propriétés et options essentielles
- Bonnes pratiques
- Conclusion
1. Prérequis techniques
Backend Laravel
- Laravel 12 avec inertiajs/inertia-laravel ≥ v2.0.10
- Routes resourceful et Form Request pour validation
- laravel/wayfinder
- Contrôleur RESTful
Frontend Vue.js
- Vue.js 3 avec Composition API
- Inertiajs/vue3 version 2.1.0+
- TypeScript configuré
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 LaravelRoute::resource('students', StudentController::class);
Génère côté frontend:
// resources/js/routes/students/index.tsexport 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:
ou
Cette syntaxe se décompose ainsi:
StudentController.storecorrespond à la méthodestorede votre contrôleur.form()retourne un objet contenant{ action: '/students', method: 'POST' }v-binddistribue ces propriétés au composant Form:actiondéfinit dynamiquement l'URL de soumission du formulaire
Avantages de Wayfinder
Type Safety: TypeScript détecte les erreurs de routes à la compilation.
// ✅ CorrectStudentController.store.form() // ❌ Erreur TypeScriptStudentController.stor.form() // Typo détectée
Paramètres dynamiques: Pour les routes avec paramètres:
<!-- Pour create/store: pas de paramètre --> <!-- Pour update: ID requis --> <!-- Pour delete: ID requis -->
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" /> <button type="submit" :disabled="processing"> {{ processing ? 'Création...' : 'Créer' }} </button></Form>
Propriétés disponibles:
errors: Erreurs de validationprocessing: État de soumissionwasSuccessful: Succès de la dernière soumissionisDirty: Formulaire modifiésetError,clearErrors: Manipulation des erreurs
Support des structures imbriquées
<Form action="/users" method="post"> <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 Wayfinderenctype="multipart/form-data": Pour les uploads de fichiers- Pas de
v-model processingpour 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> <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:
- L'utilisateur clique sur "Soumettre"
- Le formulaire reçoit l'attribut
inert - Tous les champs et boutons deviennent non interactifs
- Les styles CSS s'appliquent automatiquement via le sélecteur
:inert - Après la réponse du serveur, l'attribut
inertest 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>
. . .
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 manuelleconst submitForm = () => { if (formRef.value) { formRef.value.submit(); }}; // Réinitialisation manuelleconst resetForm = () => { if (formRef.value) { formRef.value.reset(); }}; // Auto-save toutes les 30 secondessetInterval(() => { 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.
WAFFO LELE ROSTAND