Skip to content

useDirtyForm ​

Detecta cambios sin guardar en un formulario usando comparación de snapshot JSON.

Problema que resuelve ​

Sin useDirtyForm, al cerrar un modal con cambios sin guardar, el usuario pierde su trabajo silenciosamente. Con useDirtyForm + UiFormModal confirmOnDirty, aparece un diálogo de confirmación.

API ​

ts
function useDirtyForm<T>(selector: () => T): {
  isDirty:  ComputedRef<boolean>
  snapshot: Ref<string>
  capture:  () => void
  reset:    () => void
  clear:    () => void
}

selector ​

Función que retorna el estado actual del formulario. Se evalúa reactivamente:

ts
const { isDirty, capture } = useDirtyForm(() => ({
  name: form.name,
  email: form.email,
  roleIds: [...form.roleIds],  // spread para comparar por valor, no referencia
}))

TIP

Usa spread ([...arr]) en arrays para que la comparación funcione correctamente. Sin spread, JSON.stringify de un array reactivo puede producir el mismo string aunque sus elementos hayan cambiado.

Métodos ​

MétodoQué haceCuándo llamar
capture()Guarda el estado actual como snapshot de referenciaEn openModal() — antes de abrir
reset()Actualiza el snapshot al estado actual (no cierra el modal)Post-guardado si el modal sigue abierto
clear()Limpia el snapshot → isDirty siempre falsePara forzar cierre sin confirmación

isDirty ​

Computed que compara JSON.stringify(selector()) con el snapshot capturado:

ts
const isDirty = computed(
  () => JSON.stringify(selector()) !== snapshot.value
)

true = hay cambios sin guardar. false = el formulario está igual que cuando se abrió.

Ciclo de vida típico ​

ts
const { isDirty, capture } = useDirtyForm(() => ({ ...form }))

// 1. Abrir modal — capturar estado inicial
const openEditModal = (item: Member) => {
  form.name = item.name
  form.email = item.email
  memberStore.error = null
  capture()          // ← guarda el estado como referencia
  editModal.open(item)
}

// 2. El usuario edita campos — isDirty se actualiza automáticamente

// 3. Guardar — cerrar modal (sin dirty porque ya guardamos)
const handleSubmit = async () => {
  await store.updateMember(form)
  editModal.close()
  toast.success('Guardado correctamente')
  // No necesitas llamar clear() — el modal está cerrado
}

Integración con UiFormModal ​

vue
<UiFormModal
  v-model="isEditModalOpen"
  title="Editar Miembro"
  :dirty="isDirty"
  :confirm-on-dirty="true"
  @submit="handleSubmit"
>
  <!-- Si el usuario intenta cerrar con isDirty=true,
       UiFormModal muestra: "Tienes cambios sin guardar. ¿Cerrar de todos modos?" -->
</UiFormModal>

Múltiples formularios en la misma view ​

ts
// Formulario de crear
const { isDirty: isAddDirty, capture: captureAddForm } = useDirtyForm(() => ({
  name: addForm.name,
  email: addForm.email,
}))

// Formulario de editar
const { isDirty: isEditDirty, capture: captureEditForm } = useDirtyForm(() => ({
  name: editForm.name,
  roleIds: [...editForm.roleIds],
}))

Documentación de Kaleo — plataforma SaaS multi-tenant