tianyili/skills · Archived

react-hook-form-orchestration

Use when building, refactoring, or reviewing complex React Hook Form flows, especially forms with cross-field dependencies, useWatch/watch, useEffect, setValue, trigger, resolver-driven validation, debounced API calls, dependent quote/price fetches, amount validation, or freeze/infinite-render-loop symptoms.

First seen Jul 2, 2026

Installation

$ npx skills add tianyili/skills --skill react-hook-form-orchestration

Summary

Use when building, refactoring, or reviewing complex React Hook Form flows, especially forms with cross-field dependencies, useWatch/watch, useEffect, setValue, trigger, resolver-driven validation, debounced API calls, dependent quote/price fetches, amount validation, or freeze/infinite-render-loop symptoms.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from tianyili/skills.

npx skills add tianyili/skills

Browse all from tianyili/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1
Default branch main
Open issues 1
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version0.1.0
More metadata
version
0.1.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,644 B
  • docs SUMMARY.md 346 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

React Hook Form Orchestration

Core Rule

Keep cross-field form orchestration in one controller/container layer. Form item components render fields and call explicit handlers; they do not own a distributed form state machine.

Apply This Pattern When

  • A field change resets or defaults another field.
  • A field change must revalidate another field.
  • A watched value triggers an API call, debounced mutation, or quote fetch.
  • Validation depends on derived max/min values, token decimals, balances, or quote output.
  • A form freezes or loops after typing into a field.
  • A component has useWatch/watch plus useEffect plus setValue or trigger.

Required Structure

Follow a feature-module structure that separates orchestration from fields:

  • Put orchestration in a container/controller component, not in components/formItems/*.form.tsx.
  • Keep formItems/*.form.tsx field-only: render inputs/selects/buttons, read local field state if needed, and call props callbacks.
  • Put options/constants in constants/.
  • Put reusable pure logic in utils/; no async utilities.
  • Put non-reusable one-off form logic in the controller, not in module-level hooks.

Avoid

Do not put this pattern in form items:

const value = useWatch({ control, name: 'some_field' });

useEffect(() => {
  form.setValue('other_field', nextValue, { shouldValidate: true });
  form.trigger('third_field');
}, [value, form]);

This creates a hidden graph:

watch -> render -> effect -> setValue/trigger -> formState update -> render

It becomes especially fragile when the watched result is used as an effect dependency. React Hook Form documents watch/useWatch results as render-phase optimized; use external comparison when they must drive effects.

Preferred Pattern

Make dependent transitions event-driven:

function handleTargetChainChange(chainId: string) {
  const asset = getDefaultTargetAsset(chainId);

  form.setValue('target_chain', chainId, {
    shouldDirty: true,
    shouldValidate: true,
  });
  form.setValue('target_asset', asset, {
    shouldDirty: true,
    shouldValidate: true,
  });

  if (form.getValues('recipient')) void form.trigger('recipient');
}

Then pass the handler into a field-only form item:

<TargetChainSelect
  value={targetChainId}
  onChange={handleTargetChainChange}
/>

Quote/API Effects

Keep async effects in the controller and make them key-driven:

const quoteRequest = useMemo(() => {
  if (!canQuote) return null;
  return {
    key: [chain, token, amount, recipient].join('|'),
    params,
  };
}, [canQuote, chain, token, amount, recipient]);

useEffect(() => {
  if (!quoteRequest) return;
  const timer = setTimeout(() => fetchQuote(quoteRequest.params), 500);
  return () => clearTimeout(timer);
}, [quoteRequest, fetchQuote]);

Do not scatter quote fetching across form items. Do not let quote updates immediately trigger broad validation unless a derived validation key actually changed.

Validation

  • Prefer validation based on form values and explicit derived controller state.
  • If validation max/min changes, revalidate by a stable primitive key, not by object identity.
  • Avoid render-time ref side channels when possible. If a resolver must read a ref, write it in the controller and keep revalidation guarded.
  • Avoid setValue(..., { shouldValidate: true }) inside effects caused by form subscriptions.

Review Checklist

Reject or refactor when:

  • components/formItems/*.form.tsx imports useWatch, watch, useEffect, setValue, or trigger for cross-field behavior.
  • A form item both reads one field and mutates another.
  • A dependency array includes the whole form object while the effect calls setValue or trigger.
  • A debounced API call watches many fields inside a form item.
  • Resolver behavior depends on values mutated during child render.

Accept when:

  • One controller owns useForm, watched values, derived values, dependent-field handlers, API effects, and submit disabled state.
  • Form items are dumb render components.
  • Cross-field transitions run from explicit user-event handlers.
  • Effects are keyed by stable primitive strings/numbers and guarded against repeated state writes.