← Back to archive

Repo of the Day

Khalzy/rhf-granular

Published: Feb 24, 2026

Open repository ↗

Computed values and granular subscriptions for React Hook Form.

Summary

rhf-granular is a TypeScript library that adds computed values and granular subscriptions to react-hook-form. It addresses re-render issues caused by the standard watch and useWatch hooks, which can cause components to re-render on every form change even when the value they depend on hasn't changed. The library requires React 18+ and react-hook-form 7+.

What it is useful for

This library is useful for React applications using react-hook-form where re-render performance is a concern. It fills gaps that the built-in useWatch and compute option don't cover:

  • Form-wide selectors: access values, errors, isSubmitting, isValidating and other form meta together in one selector. The built-in compute option only receives field values.
  • Field state without controlled inputs: read isTouched, isDirty, or error for a field without using useController.
  • Side effects without render cycles: trigger API calls or analytics when form values change, without the render-then-effect pattern.
  • Derived values that only re-render when the result changes: calculations like price = base * seats that would otherwise require useMemo + useWatch boilerplate.

How engineers can use it

Setup:

npm install rhf-granular

The library exposes three hooks:

  1. useFormSelector — Subscribe to a derived value; the component only re-renders when the result changes.
const isEnterprise = useFormSelector(
  control,
  (s) => s.values.plan === "enterprise"
);
  1. useFormValue — Subscribe to a single field by dot-notation path.
const plan = useFormValue(control, "plan");
const firstName = useFormValue(control, "users.0.firstName");
  1. useFormEffect — Run side effects on form state changes without causing re-renders.
useFormEffect(control, ({ values, isDirty }) => {
  if (values.plan === "enterprise" && isDirty) {
    trackEvent("enterprise_plan_edited");
  }
});

Documented limitations and patterns to avoid:

  • Components render twice on mount (four times in StrictMode) due to standard RHF initialization. The README states this does not happen in production builds.
  • Reading any property from formState at the top level subscribes the component to every form state change, causing the whole tree to re-render on every keystroke.
  • useFormContext is not safe for reactive reads; it's only safe for pulling stable references like control, register, handleSubmit, and setValue.
  • Destructuring fields from useFieldArray re-renders on every array mutation. For metadata like count, use a selector instead.

The README includes a detailed "footguns" section that should be read before integrating the library, as several common patterns will silently cause full re-renders.