Skip to content

useFormState

Subscribe to specific form state changes for optimized re-renders.

Import

typescript
import { useFormState } from '@vuehookform/core'

Usage

typescript
const { isDirty, errors } = useFormState({ control })

When to Use

Use useFormState when you need to:

  • Access form state in a child component
  • Subscribe to specific state properties only
  • Optimize re-renders by limiting subscriptions

For most cases, accessing formState directly from useForm is sufficient.

Options

control

Type: Control<T>
Required: Yes

The control object from useForm.

typescript
const { control } = useForm({ schema })
const state = useFormState({ control })

Return Values

All properties are reactive refs:

errors

typescript
errors: Ref<FieldErrors<T>>

Current validation errors.

typescript
const { errors } = useFormState({ control })

// In template
<span v-if="errors.email">{{ errors.email }}</span>

isDirty

typescript
isDirty: Ref<boolean>

True if any field has been modified from default values.

isValid

typescript
isValid: Ref<boolean>

True if the form has no validation errors.

isSubmitting

typescript
isSubmitting: Ref<boolean>

True while form submission is in progress.

isSubmitted

typescript
isSubmitted: Ref<boolean>

True after the form has been submitted at least once.

isSubmitSuccessful

typescript
isSubmitSuccessful: Ref<boolean>

True if the last submission completed without errors.

submitCount

typescript
submitCount: Ref<number>

Number of times the form has been submitted.

touchedFields

typescript
touchedFields: Ref<Set<string>>

Set of field names that have been touched (blurred).

dirtyFields

typescript
dirtyFields: Ref<Set<string>>

Set of field names with values different from defaults.

Example: Submit Button Component

vue
<!-- SubmitButton.vue -->
<script setup lang="ts">
import { useFormState } from '@vuehookform/core'
import type { Control } from '@vuehookform/core'

const props = defineProps<{
  control: Control<any>
}>()

const { isSubmitting, isDirty, isValid } = useFormState({
  control: props.control,
})
</script>

<template>
  <button type="submit" :disabled="isSubmitting || !isDirty" :class="{ loading: isSubmitting }">
    <span v-if="isSubmitting">Saving...</span>
    <span v-else>Save Changes</span>
  </button>
</template>

Example: Error Summary Component

vue
<!-- ErrorSummary.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { useFormState } from '@vuehookform/core'
import type { Control } from '@vuehookform/core'

const props = defineProps<{
  control: Control<any>
}>()

const { errors, isSubmitted } = useFormState({
  control: props.control,
})

const errorList = computed(() => {
  return Object.entries(errors.value).map(([field, message]) => ({
    field,
    message,
  }))
})

const hasErrors = computed(() => errorList.value.length > 0)
</script>

<template>
  <div v-if="isSubmitted && hasErrors" class="error-summary" role="alert">
    <h3>Please fix the following errors:</h3>
    <ul>
      <li v-for="error in errorList" :key="error.field">
        <strong>{{ error.field }}:</strong> {{ error.message }}
      </li>
    </ul>
  </div>
</template>

Example: Form Status Indicator

vue
<!-- FormStatus.vue -->
<script setup lang="ts">
import { useFormState } from '@vuehookform/core'
import type { Control } from '@vuehookform/core'

const props = defineProps<{
  control: Control<any>
}>()

const { isDirty, isValid, isSubmitting } = useFormState({
  control: props.control,
})
</script>

<template>
  <div class="form-status">
    <span v-if="isSubmitting" class="status submitting"> Submitting... </span>
    <span v-else-if="isDirty && !isValid" class="status invalid"> Form has errors </span>
    <span v-else-if="isDirty" class="status dirty"> Unsaved changes </span>
    <span v-else class="status clean"> No changes </span>
  </div>
</template>

Comparison with formState

ApproachUse Case
formState from useFormMost common, direct access in form component
useFormStateChild components, selective subscriptions

Released under the MIT License.