---
title: "Submission and reset"
description: "Submit validated data, handle errors thrown by your submit handler, validate without submitting, and reset values, errors, and state."
canonical_url: "https://notformdocs.vercel.app/docs/guides/submission-and-reset"
---

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

## Submit with Form

`Form` renders a native `<form>`. It prevents the browser's page navigation on submit, calls `form.submit`, and handles native reset by calling `form.reset`.

```vue
<template>
  <Form :form="form">
    <!-- fields -->
    <button
      type="submit"
      :disabled="form.isSubmitting"
    >
      {{ form.isSubmitting ? 'Saving...' : 'Save' }}
    </button>

    <button
      type="reset"
      :disabled="form.isSubmitting"
    >
      Reset
    </button>
  </Form>
</template>
```

## What a submit does

1. A submit while another is still running is **ignored**.
2. `isSubmitting` becomes `true`, `submitCount` increases, and every field counts as touched.
3. The whole form is validated.
4. If a value changed while validation was running, validation runs again on the new values.
5. If the schema reports issues, they replace all errors and `onSubmit` is **not** called. `isSubmitted` is still `true`.
6. If validation passes, errors are cleared and `onSubmit` is awaited with the validated **output**.
7. `isSubmitting` becomes `false` again, even if `onSubmit` throws.

`onSubmit` can be sync or async. `isSubmitting` stays `true` for the whole duration, so you can use it to disable the button or show a spinner.

```ts
const form = useForm({
  initialValues,
  async onSubmit(data) {
    await api.save(data) // data is SchemaOutput<typeof schema>
  },
  schema,
})
```

## Errors thrown in onSubmit

If `onSubmit` throws or rejects, `isSubmitting` is reset and the error propagates out of `form.submit()`.

- When `Form` triggers the submit, the rejection goes to Vue's error handler (`app.config.errorHandler`, or `vue:error` in Nuxt) and your UI gets no error message.
- When you call `form.submit()` yourself, the returned promise rejects.

Handle expected failures inside `onSubmit` and turn them into form errors:

```ts
const form = useForm({
  async onSubmit(data) {
    try {
      await api.save(data)
    } catch {
      form.setError({ message: 'We could not save your changes. Try again.' })
    }
  }
})
```

The [Server errors recipe](https://notformdocs.vercel.app/docs/recipes/server-errors) shows how to map field-level responses as well.

## Submit programmatically

Call `form.submit()` when a submit does not start from `<Form>`, for example from a keyboard shortcut or a button outside the form element. It validates before invoking `onSubmit`.

```ts
await form.submit()
```

Passed a native submit event, it also calls `preventDefault()`. If you do not want to use `Form`, this is the equivalent markup:

```vue
<template>
  <form
    @submit="form.submit"
    @reset.prevent="form.reset"
  >
  <!-- fields -->
  </form>
</template>
```

## Validate without submitting

`form.validate()` validates the whole form, updates `form.errors`, and does not call `onSubmit`.

```ts
const result = await form.validate()

if (result.issues) {
  // show a message, scroll to the first error, etc.
} else {
  result.value // parsed output
}
```

## Reset the form

`form.reset()` returns the form to its baseline:

- `values` return to the current `initialValues`;
- `errors` return to the current `initialErrors`;
- touched state and `submitCount` are cleared, so `isTouched` and `isSubmitted` become `false`;
- pending debounced validations and in-flight validations are cancelled, along with any submit in progress;
- array items receive fresh identities, and `resetCount` increases.

Use `type="reset"` inside `<Form>`, or call `form.reset()` yourself.

<note>

Reset restores the **baseline**, not an empty form. If the baseline holds loaded data, reset returns to that data.

</note>

### Replace the baseline

Pass values, errors, or both to start from a new baseline. The values you supply **replace** the old baseline. They are not merged with it. An omitted argument keeps its current baseline.

```ts
form.reset(
  { email: 'jane@example.com' },
  [{ message: 'Verify this address.', path: ['email'] }],
)
```

The next plain `form.reset()` returns to this new baseline. Use this when saved data should become the new starting point for dirty tracking. See [Editing existing data](https://notformdocs.vercel.app/docs/recipes/editing-existing-data).

### Remount inputs after reset

`form.resetCount` increases on every reset. Use it as a `:key` on a subtree whose uncontrolled state should be discarded.

```vue
<template>
  <MyThirdPartyEditor
    :key="form.resetCount"
    v-model="form.values.body"
  />
</template>
```

### Reset after a successful submit

NotForm does not reset on its own after `onSubmit`. Do it when it suits your flow:

```ts
const form = useForm({
  async onSubmit(data) {
    await api.create(data)
    form.reset() // back to a blank form
  // or: form.reset(data) to make the saved data the new baseline
  }
})
```

## Next steps

- [Form state](https://notformdocs.vercel.app/docs/guides/form-state) lists every flag the form exposes.
- [Server errors](https://notformdocs.vercel.app/docs/recipes/server-errors)

## Sitemap

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