---
title: "Editing existing data"
description: "Load a saved record into a form, track unsaved changes, and make the saved result the new baseline."
canonical_url: "https://notformdocs.vercel.app/docs/recipes/editing-existing-data"
---

> Index of every page on this site: [llms.txt](https://notformdocs.vercel.app/llms.txt)

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

```ts
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.

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

```ts
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

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

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

```ts
watch(profile, (value) => {
  form.reset(value)
})
```

- Input and output shapes can differ. `reset` and `initialValues` always use the **input** shape.

## Sitemap

See the full [sitemap](https://notformdocs.vercel.app/sitemap.md) for all pages.
