Watch
React to form field value changes in real-time.
Basic Usage
Use watch from useForm to observe field values:
vue
<script setup>
import { useForm } from '@vuehookform/core'
const { register, watch } = useForm({ schema })
// Watch single field
const email = watch('email')
// Watch multiple fields
const credentials = watch(['email', 'password'])
// Watch all fields
const allValues = watch()
</script>
<template>
<input v-bind="register('email')" />
<p>Current email: {{ email }}</p>
</template>Watch Modes
Single Field
typescript
const email = watch('email')
// Returns: ComputedRef<string>
// Use in template or computed
console.log(email.value)Multiple Fields
typescript
const credentials = watch(['email', 'password'])
// Returns: ComputedRef<{ email?: string, password?: string }>
// Access individual values via the object
console.log(credentials.value.email, credentials.value.password)All Fields
typescript
const allValues = watch()
// Returns: ComputedRef<FormValues>
// Access any field
console.log(allValues.value.email)Common Use Cases
Live Preview
vue
<script setup>
import { useForm } from '@vuehookform/core'
const { register, handleSubmit, watch } = useForm({ schema })
const formData = watch()
</script>
<template>
<div class="editor">
<form @submit="handleSubmit(onSubmit)">
<input v-bind="register('title')" placeholder="Title" />
<textarea v-bind="register('content')" placeholder="Content"></textarea>
</form>
<div class="preview">
<h1>{{ formData.title || 'Untitled' }}</h1>
<p>{{ formData.content || 'Start writing...' }}</p>
</div>
</div>
</template>Computed Values
vue
<script setup>
import { computed } from 'vue'
import { useForm } from '@vuehookform/core'
const { register, watch } = useForm({ schema })
const quantity = watch('quantity')
const price = watch('price')
const total = computed(() => {
return (quantity.value || 0) * (price.value || 0)
})
const formattedTotal = computed(() => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(total.value)
})
</script>
<template>
<input v-bind="register('quantity')" type="number" />
<input v-bind="register('price')" type="number" step="0.01" />
<p>Total: {{ formattedTotal }}</p>
</template>Conditional Logic
vue
<script setup>
import { useForm } from '@vuehookform/core'
const { register, watch } = useForm({ schema })
const shippingMethod = watch('shippingMethod')
</script>
<template>
<select v-bind="register('shippingMethod')">
<option value="standard">Standard</option>
<option value="express">Express</option>
<option value="pickup">Store Pickup</option>
</select>
<!-- Show address fields only for delivery -->
<div v-if="shippingMethod !== 'pickup'">
<input v-bind="register('address')" placeholder="Address" />
<input v-bind="register('city')" placeholder="City" />
</div>
<!-- Show store selection for pickup -->
<div v-else>
<select v-bind="register('storeId')">
<option value="">Select a store</option>
<!-- Store options -->
</select>
</div>
</template>Character Counter
vue
<script setup>
import { computed } from 'vue'
import { useForm } from '@vuehookform/core'
const { register, watch } = useForm({ schema })
const bio = watch('bio')
const charCount = computed(() => bio.value?.length || 0)
const maxChars = 500
const remaining = computed(() => maxChars - charCount.value)
</script>
<template>
<div class="bio-field">
<label>Bio</label>
<textarea v-bind="register('bio')" :maxlength="maxChars"></textarea>
<span :class="{ warning: remaining < 50 }"> {{ remaining }} characters remaining </span>
</div>
</template>Dependent Fields
vue
<script setup>
import { watchEffect } from 'vue'
import { useForm } from '@vuehookform/core'
const { register, watch, setValue } = useForm({ schema })
const country = watch('country')
// Update available states when country changes
const availableStates = computed(() => {
return getStatesForCountry(country.value)
})
// Clear state when country changes
watchEffect(() => {
if (country.value) {
setValue('state', '')
}
})
</script>Form Dirty State Indicator
vue
<script setup>
import { computed } from 'vue'
import { useForm } from '@vuehookform/core'
const { register, watch } = useForm({
schema,
defaultValues: {
name: 'Original Name',
email: 'original@email.com',
},
})
const defaultValues = {
name: 'Original Name',
email: 'original@email.com',
}
const currentValues = watch()
const hasChanges = computed(() => {
return JSON.stringify(currentValues.value) !== JSON.stringify(defaultValues)
})
</script>
<template>
<div>
<span v-if="hasChanges" class="unsaved-indicator"> Unsaved changes </span>
<form>
<!-- fields -->
</form>
</div>
</template>useWatch Composable
For child components, use the useWatch composable:
vue
<!-- PasswordStrength.vue -->
<script setup>
import { computed } from 'vue'
import { useWatch } from '@vuehookform/core'
const props = defineProps<{
control: Control<any>
}>()
const password = useWatch({
control: props.control,
name: 'password',
defaultValue: '',
})
const strength = computed(() => {
const p = password.value
if (!p) return { level: 0, label: 'None' }
if (p.length < 8) return { level: 1, label: 'Weak' }
if (/[A-Z]/.test(p) && /[0-9]/.test(p)) return { level: 3, label: 'Strong' }
return { level: 2, label: 'Medium' }
})
</script>
<template>
<div class="password-strength">
<div class="strength-bar" :data-level="strength.level" />
<span>{{ strength.label }}</span>
</div>
</template>Usage:
vue
<script setup>
import { useForm } from '@vuehookform/core'
import PasswordStrength from './PasswordStrength.vue'
const { control, register } = useForm({ schema })
</script>
<template>
<input v-bind="register('password')" type="password" />
<PasswordStrength :control="control" />
</template>Performance Tips
- Watch specific fields instead of all values when possible
- Use computed properties for derived values to leverage Vue's caching
- Avoid expensive operations in watch callbacks
- Consider debouncing for real-time features like auto-save
typescript
import { watchDebounced } from '@vueuse/core'
const formData = watch()
watchDebounced(
formData,
(values) => {
autoSave(values)
},
{ debounce: 1000 },
)Next Steps
- Learn about Programmatic Control
- Explore TypeScript integration
