21

Editing existing data

Load a saved record into a form, track unsaved changes, and make the saved result the new baseline.

An edit form starts from data that already exists. The key idea is the baseline: the values isDirty compares against and reset() returns to. Loaded data should become the baseline.

When the data is ready before the form

If the record is available when the form is created, pass it as initialValues.

const { data: profile } = await useFetch('/api/profile') // Nuxt

const form = useForm({
  initialValues: profile.value ?? { bio: '', displayName: '' },
  async onSubmit(values) {
    const saved = await $fetch('/api/profile', { body: values, method: 'PUT' })
    form.reset(saved)
  },
  schema: profileSchema,
})

In plain Vue, wait to render the form until the data has loaded, then pass it down.

<template>
  <ProfileForm
    v-if="profile"
    :profile="profile"
  />
</template>

When the data arrives after the form exists

Create the form with defaults and call reset(values) once the data arrives. This replaces the baseline, so isDirty is false right after loading.

const form = useForm({
  initialValues: { bio: '', displayName: '' },
  async onSubmit(data) {
    const saved = await api.updateProfile(data)
    form.reset(saved)
  },
  schema: profileSchema,
})

onMounted(async () => {
  form.reset(await api.getProfile())
})

reset(values) also restores errors to the baseline, clears touched state, bumps resetCount, and gives array items fresh ids.

Make the saved data the new baseline

After a successful save, call form.reset(saved) with what the server stored. The form then reports isDirty === false, and a later plain reset() returns to the saved version, not the original.

If the server returns exactly what you sent, form.reset(data) inside onSubmit is enough. Remember that data is the schema output. If your schema transforms values, reset with an object in the input shape instead.

Warn about unsaved changes

<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave(() => {
  if (form.isDirty && !confirm('Discard unsaved changes?')) {
    return false
  }
})

function warnBeforeClosing(event: BeforeUnloadEvent) {
  if (form.isDirty) {
    event.preventDefault()
  }
}

onMounted(async () => {
  window.addEventListener('beforeunload', warnBeforeClosing)
})
onBeforeUnmount(async () => {
  window.removeEventListener('beforeunload', warnBeforeClosing)
})
</script>

Show save state

<template>
  <button
    type="submit"
    :disabled="!form.isDirty || form.isSubmitting"
  >
    {{ form.isSubmitting ? 'Saving...' : 'Save changes' }}
  </button>

  <button
    type="reset"
    :disabled="!form.isDirty"
  >
    Discard changes
  </button>
</template>

Tips

  • Keep every bound field present in the loaded object, using '', false, or [] where the server sends null. Missing keys reach the schema as undefined.
  • Reload the baseline when the underlying record changes. For example,
watch(profile, (value) => {
  form.reset(value)
})
  • Input and output shapes can differ. reset and initialValues always use the input shape.
Copyright © 2026 NotForm
Published under MIT License