19. Cómo reutilizar formularios de inertia
19: Cómo Reutilizar Formularios en Inertia
En este tutorial, aprenderemos a optimizar nuestro código extrayendo la lógica de formularios para reutilizarla entre las vistas de creación y edición de chatbots. Esto nos permitirá mantener un código más limpio, fácil de mantener y menos propenso a errores.
1. Identificar el Problema: Código Duplicado
Actualmente, nuestras vistas de creación y edición comparten prácticamente el mismo formulario:
Crear: resources/js/Pages/Chatbots/Create.vue
<script setup> import { useForm } from '@inertiajs/vue3'; const form = useForm({ name: '', description: '', }); const handleSubmit = () => { form.post(route('chatbots.store')); }; </script>
Editar: resources/js/Pages/Chatbots/Edit.vue
<script setup> import { useForm } from '@inertiajs/vue3'; const props = defineProps({ chatbot: Object }); const form = useForm({ name: props.chatbot.name, description: props.chatbot.description, }); const handleSubmit = () => { form.put(route('chatbots.update', props.chatbot.id)); }; </script>
Ambas vistas tienen la misma estructura de formulario, pero con diferentes datos iniciales y diferentes métodos de envío. Vamos a extraer esta lógica para reutilizarla.
2. Crear el Archivo de Formulario Reutilizable
2.1. Estructura de Carpetas
Crearemos una nueva carpeta dentro de resources/js para almacenar nuestros formularios reutilizables:
resources/js/ ├── Pages/ │ ├── Chatbots/ │ │ ├── Create.vue │ │ ├── Edit.vue │ │ └── Index.vue ├── forms/ │ └── chatbot.js ← Nuevo archivo
2.2. Crear el Formulario Base
resources/js/forms/chatbot.js
import { useForm } from '@inertiajs/vue3'; /** * Función para crear el formulario de chatbot * @param {Object} chatbot - Datos del chatbot (vacío para creación, con datos para edición) * @returns {Object} - Instancia del formulario de Inertia */ export const createForm = (chatbot = {}) => { return useForm({ name: chatbot.name || '', description: chatbot.description || '', }); }; // También podemos exportar como default si preferimos export default createForm;
2.3. Crear Funciones de Envío
Añadimos funciones para manejar el envío del formulario tanto para creación como para actualización:
resources/js/forms/chatbot.js
import { useForm } from '@inertiajs/vue3'; // Función para obtener las opciones por defecto const getDefaultOptions = (customOptions = {}) => { const defaultOptions = { preserveScroll: true, preserveState: true, }; return { ...defaultOptions, ...customOptions }; }; // 1. Crear el formulario export const createForm = (chatbot = {}) => { return useForm({ name: chatbot.name || '', description: chatbot.description || '', }); }; // 2. Enviar para crear (POST) export const store = (form, options = {}) => { const finalOptions = getDefaultOptions(options); return form.post(route('chatbots.store'), finalOptions); }; // 3. Enviar para actualizar (PUT) export const update = (form, chatbotId, options = {}) => { const finalOptions = getDefaultOptions(options); return form.put(route('chatbots.update', chatbotId), finalOptions); };
3. Implementar en la Vista de Creación
Ahora simplificamos la vista de creación para usar nuestro formulario reutilizable.
resources/js/Pages/Chatbots/Create.vue
<template> <div class="container mx-auto px-4 py-8"> <div class="max-w-2xl mx-auto"> <h1 class="text-2xl font-bold mb-6">Crear Nuevo Chatbot</h1> <form @submit.prevent="handleSubmit"> <div class="mb-4"> <label for="name" class="block text-sm font-medium text-gray-700 mb-2"> Nombre del Chatbot </label> <input type="text" id="name" v-model="form.name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ej: Asistente de Ventas"> <p v-if="form.errors.name" class="mt-1 text-sm text-red-600"> {{ form.errors.name }} </p> </div> <div class="mb-4"> <label for="description" class="block text-sm font-medium text-gray-700 mb-2"> Descripción </label> <textarea id="description" v-model="form.description" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Describe el propósito de este chatbot..."></textarea> <p v-if="form.errors.description" class="mt-1 text-sm text-red-600"> {{ form.errors.description }} </p> </div> <div class="flex items-center justify-end"> <Link :href="route('chatbots.index')" class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 mr-2"> Cancelar </Link> <button type="submit" :disabled="form.processing" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition disabled:opacity-50"> Crear Chatbot </button> </div> </form> </div> </div> </template> <script setup> import { Link } from '@inertiajs/vue3'; import { createForm, store } from '@/forms/chatbot'; // Crear el formulario con datos vacíos const form = createForm(); const handleSubmit = () => { store(form); }; </script>
4. Implementar en la Vista de Edición
De manera similar, simplificamos la vista de edición.
resources/js/Pages/Chatbots/Edit.vue
<template> <div class="container mx-auto px-4 py-8"> <div class="max-w-2xl mx-auto"> <h1 class="text-2xl font-bold mb-6">Editar Chatbot</h1> <form @submit.prevent="handleSubmit"> <div class="mb-4"> <label for="name" class="block text-sm font-medium text-gray-700 mb-2"> Nombre del Chatbot </label> <input type="text" id="name" v-model="form.name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> <p v-if="form.errors.name" class="mt-1 text-sm text-red-600"> {{ form.errors.name }} </p> </div> <div class="mb-4"> <label for="description" class="block text-sm font-medium text-gray-700 mb-2"> Descripción </label> <textarea id="description" v-model="form.description" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea> <p v-if="form.errors.description" class="mt-1 text-sm text-red-600"> {{ form.errors.description }} </p> </div> <div class="flex items-center justify-end"> <Link :href="route('chatbots.index')" class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 mr-2"> Cancelar </Link> <button type="submit" :disabled="form.processing" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition disabled:opacity-50"> Actualizar Chatbot </button> </div> </form> </div> </div> </template> <script setup> import { Link } from '@inertiajs/vue3'; import { createForm, update } from '@/forms/chatbot'; const props = defineProps({ chatbot: Object }); // Crear el formulario con los datos del chatbot existente const form = createForm(props.chatbot); const handleSubmit = () => { update(form, props.chatbot.id); }; </script>
5. Extraer Opciones de Configuración
Podemos mejorar nuestro archivo de formulario para aceptar opciones de configuración como onSuccess, onError, etc.
5.1. Actualizar el Archivo de Formulario
resources/js/forms/chatbot.js
import { useForm } from '@inertiajs/vue3'; // Opciones por defecto para los envíos const getDefaultOptions = (customOptions = {}) => { const defaultOptions = { preserveScroll: true, preserveState: true, onSuccess: () => {}, onError: () => {}, onFinish: () => {}, }; return { ...defaultOptions, ...customOptions }; }; // Crear el formulario export const createForm = (chatbot = {}) => { return useForm({ name: chatbot.name || '', description: chatbot.description || '', }); }; // Enviar para crear export const store = (form, options = {}) => { const finalOptions = getDefaultOptions(options); return form.post(route('chatbots.store'), finalOptions); }; // Enviar para actualizar export const update = (form, chatbotId, options = {}) => { const finalOptions = getDefaultOptions(options); return form.put(route('chatbots.update', chatbotId), finalOptions); };
5.2. Usar Opciones en las Vistas
Ejemplo en la vista de creación:
<script setup> import { createForm, store } from '@/forms/chatbot'; const form = createForm(); const handleSubmit = () => { store(form, { onSuccess: () => { // Resetear el formulario después de crear form.reset(); // Mostrar notificación toast.success('¡Chatbot creado exitosamente!'); }, onError: (errors) => { console.error('Errores de validación:', errors); } }); }; </script>
Ejemplo en la vista de edición:
<script setup> import { createForm, update } from '@/forms/chatbot'; const props = defineProps({ chatbot: Object }); const form = createForm(props.chatbot); const handleSubmit = () => { update(form, props.chatbot.id, { preserveScroll: false, // Sobrescribir opción por defecto onSuccess: (page) => { toast.success('¡Chatbot actualizado exitosamente!'); } }); }; </script>
6. Opciones Avanzadas: Enlace de Callbacks
También podemos permitir callbacks personalizados en el formulario:
resources/js/forms/chatbot.js
import { useForm } from '@inertiajs/vue3'; // Función para mezclar opciones const mergeOptions = (defaultOptions, customOptions) => { return { ...defaultOptions, ...customOptions }; }; // Función que crea el formulario con callbacks personalizados export const createFormWithCallbacks = (chatbot = {}, callbacks = {}) => { const form = useForm({ name: chatbot.name || '', description: chatbot.description || '', }); // Podemos agregar métodos personalizados al formulario form.handleSubmit = (action, options = {}) => { const defaultOptions = { preserveScroll: true, preserveState: true, onSuccess: callbacks.onSuccess || (() => {}), onError: callbacks.onError || (() => {}), onFinish: callbacks.onFinish || (() => {}), }; const finalOptions = mergeOptions(defaultOptions, options); if (chatbot.id) { return form.put(route('chatbots.update', chatbot.id), finalOptions); } else { return form.post(route('chatbots.store'), finalOptions); } }; return form; }; // Exportar las funciones principales export const createForm = (chatbot = {}) => { return useForm({ name: chatbot.name || '', description: chatbot.description || '', }); }; export const store = (form, options = {}) => { const defaultOptions = { preserveScroll: true, preserveState: true, }; return form.post(route('chatbots.store'), { ...defaultOptions, ...options }); }; export const update = (form, chatbotId, options = {}) => { const defaultOptions = { preserveScroll: true, preserveState: true, }; return form.put(route('chatbots.update', chatbotId), { ...defaultOptions, ...options }); };
Uso en la vista:
<script setup> import { createFormWithCallbacks } from '@/forms/chatbot'; const props = defineProps({ chatbot: Object }); const form = createFormWithCallbacks(props.chatbot, { onSuccess: () => { toast.success('¡Operación exitosa!'); }, onError: (errors) => { toast.error('Error al guardar'); } }); const handleSubmit = () => { form.handleSubmit(); }; </script>
7. Beneficios de esta Abstracción
Al implementar esta reutilización de formularios, obtenemos varios beneficios:
7.1. Código más Limpio
Menos duplicación de código
Las vistas son más concisas y fáciles de leer
La lógica de negocio está centralizada
7.2. Mayor Mantenibilidad
Si cambia la estructura del formulario, solo debemos modificar un archivo
Las validaciones y reglas de negocio están en un solo lugar
Fácil de extender con nuevas funcionalidades
7.3. Consistencia
Todos los formularios de chatbot tienen el mismo comportamiento
Las opciones de envío son consistentes
Los mensajes de error se manejan de manera uniforme
7.4. Reutilización
El mismo formulario se puede usar en diferentes contextos
Fácil de integrar en modales o componentes secundarios
Se puede extender para otros tipos de recursos
8. Estructura Final del Proyecto
resources/js/ ├── Pages/ │ ├── Chatbots/ │ │ ├── Create.vue ← Usa createForm y store │ │ ├── Edit.vue ← Usa createForm y update │ │ ├── Index.vue │ │ └── Show.vue │ └── Layouts/ │ └── Authenticated.vue ├── forms/ │ └── chatbot.js ← Lógica de formulario centralizada ├── Components/ │ ├── ChatbotForm.vue ← Opcional: componente visual reutilizable │ └── ... └── app.js
8.1. Opción Extra: Componente Visual
Si queremos reutilizar también la parte visual del formulario:
resources/js/Components/ChatbotForm.vue
<template> <form @submit.prevent="handleSubmit"> <div class="mb-4"> <label for="name" class="block text-sm font-medium text-gray-700 mb-2"> Nombre del Chatbot </label> <input type="text" id="name" v-model="form.name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> <p v-if="form.errors.name" class="mt-1 text-sm text-red-600"> {{ form.errors.name }} </p> </div> <div class="mb-4"> <label for="description" class="block text-sm font-medium text-gray-700 mb-2"> Descripción </label> <textarea id="description" v-model="form.description" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea> <p v-if="form.errors.description" class="mt-1 text-sm text-red-600"> {{ form.errors.description }} </p> </div> <div class="flex items-center justify-end"> <Link :href="cancelRoute || route('chatbots.index')" class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 mr-2"> Cancelar </Link> <button type="submit" :disabled="form.processing" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition disabled:opacity-50"> {{ submitText || 'Guardar' }} </button> </div> </form> </template> <script setup> import { Link } from '@inertiajs/vue3'; const props = defineProps({ form: Object, submitText: String, cancelRoute: String, handleSubmit: Function }); </script>
Uso en Create.vue:
<template> <div class="container mx-auto px-4 py-8"> <div class="max-w-2xl mx-auto"> <h1 class="text-2xl font-bold mb-6">Crear Nuevo Chatbot</h1> <ChatbotForm :form="form" submit-text="Crear Chatbot" :handle-submit="handleSubmit" /> </div> </div> </template> <script setup> import { createForm, store } from '@/forms/chatbot'; import ChatbotForm from '@/Components/ChatbotForm.vue'; const form = createForm(); const handleSubmit = () => { store(form); }; </script>
9. Resumen y Mejores Prácticas
Lo que hemos logrado:
✅ Centralización de la lógica de formularios
✅ Reutilización entre vistas de creación y edición
✅ Soporte para opciones de configuración
✅ Callbacks personalizados (onSuccess, onError, etc.)
✅ Código más mantenible y escalable
Mejores Prácticas:
Mantén la lógica de formularios separada de las vistas
Usa opciones por defecto sensatas (como
preserveScroll: true)Permite sobrescribir opciones cuando sea necesario
Documenta las funciones con JSDoc o comentarios claros
Exporta solo lo necesario desde el archivo de formulario
Considera crear componentes visuales para reutilizar aún más
Próximos Pasos:
Agregar más campos al formulario (configuración, personalización, etc.)
Implementar validación en tiempo real
Añadir manejo de archivos (imágenes, logos)
Crear formularios para otros recursos (usuarios, categorías, etc.)
Conclusión
¡Excelente trabajo! Ahora tienes un sistema de formularios reutilizables que hace tu código más limpio, mantenible y profesional. Esta abstracción te permitirá:
Agregar nuevos campos fácilmente
Mantener consistencia en toda la aplicación
Reducir significativamente el código duplicado
Facilitar las pruebas y el mantenimiento
Este patrón es especialmente útil en aplicaciones grandes donde múltiples vistas comparten formularios similares. ¡Continúa aplicando estos principios en tu desarrollo
Comentarios
Publicar un comentario