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

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

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:

text
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

javascript
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

javascript
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

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

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

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

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

vue
<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

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

vue
<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

text
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

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:

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:

  1. Mantén la lógica de formularios separada de las vistas

  2. Usa opciones por defecto sensatas (como preserveScroll: true)

  3. Permite sobrescribir opciones cuando sea necesario

  4. Documenta las funciones con JSDoc o comentarios claros

  5. Exporta solo lo necesario desde el archivo de formulario

  6. 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

Entradas más populares de este blog

1. Introducción al curso de Chatbots personalizados con Laravel & OpenAI

2. Prerequisitos para el Curso de Chatbots con Laravel y OpenAI

4. Herramientas de Calidad