--- title: Use subscribe() to React to Form Changes Outside the React Lifecycle impact: HIGH impactDescription: eliminates re-renders for non-UI consumers like analytics, autosave, telemetry tags: sub, subscribe, side-effects, analytics, autosave, useForm --- ## Use subscribe() to React to Form Changes Outside the React Lifecycle Introduced in v7.55.0, `useForm().subscribe(...)` registers a callback that fires on form state or value changes **without causing any re-renders**. Use it when the consumer of the change is not a UI element — analytics, autosave to localStorage, debounced telemetry, sending drafts to a server. `useWatch` and `watch` are still right for things that paint to screen; `subscribe` is right for everything else. **Incorrect (using useWatch to drive a non-UI side-effect — re-renders the form on every keystroke):** ```typescript function ProfileForm() { const { register, handleSubmit, control } = useForm() const values = useWatch({ control }) // Every keystroke re-renders ProfileForm useEffect(() => { analytics.track('profile_field_edited', { values }) // Fires on every render }, [values]) return (
) } ``` **Correct (subscribe() runs the side-effect with zero re-renders):** ```typescript function ProfileForm() { const { register, handleSubmit, subscribe } = useForm() useEffect(() => { const unsubscribe = subscribe({ formState: { values: true }, callback: ({ values, name }) => { analytics.track('profile_field_edited', { field: name, values }) }, }) return unsubscribe }, [subscribe]) return (
) } ``` **Subscribing to specific fields with formState slices (e.g. dirty-aware autosave):** ```typescript function DraftEditor() { const { register, subscribe } = useForm({ defaultValues: loadDraft(), }) useEffect(() => { const unsubscribe = subscribe({ name: ['title', 'body'], formState: { values: true, isDirty: true }, callback: ({ values, isDirty }) => { if (isDirty) debouncedSaveDraft(values) }, }) return unsubscribe }, [subscribe]) return ( <>