react-hook-form のドキュメント見ていて useForm に onChange がないことに気づいた
個別の onChange でやれってことかもしれないけど 相互に関連する入力が多くて連鎖して変化する場合にそれは使いづらい
A を変更したら B も変わって B と C が空欄になったら D もクリアして みたいなルール
form 全体の onChange イベントでまとめて処理したい

渡せる物を探すと resolver というのがあって values を受け取って values と errors を返す関数を設定できるみたい
これがまとめて変更管理するのを兼ねてるのかと思ったけど values に修正済みを渡しても form の値に変化なし
バリデーション用らしいし 実際の入力値に影響は与えられないみたい

自分で作ってる簡単なフォーム制御するものだと逆に reducer 風にまとめて扱うもののみだから この制限があるのはかなりつらめ
そういう方針のものなんだろうと諦めたところで watch を発見
変更時に再レンダリングするためのものって思ってたけど 再レンダリングなしでコールバック関数を呼び出してもらう使い方もできた

const Component = () => {
const { register, watch, setValue } = useForm()

watch((values, { name, type }) => {
console.log(values, name, type)
const set = (k, v) => {
if (values[k] === v) return
setValue(k, v)
}

if (name === "A") {
set("B", values.A)
}
if (name === "B" || name === "C") {
if (values.B === "" && values.C === "") {
set("D", "")
}
}
})

// ...
}

setValue を使った変更でも watch のコールバックが呼び出されたので 連鎖して変更するときにユーザーの入力で呼び出されたときに一度にまとめて setValue せず ひとつずつ setValue して毎回 name を見ての判定にもできる(無限ループ注意)
A が変われば B も変わり B が変われば C も変わるときに A の変更を検知したら B と C を一度に書き換えず B だけ変える
B が変わればまたコールバックが呼び出されて B の変更なので C を変える という感じ



(注)
この例だと手抜きで省略してるけど コールバック登録型の watch は useEfffect の中でしないとだめ
再レンダリング時にリスナが上書きされたりはせず毎回追加で登録される
レンダリングのたびコールバック関数が呼び出される回数が増えて重くなる
useEffect で初回だけ実行するようにしないといけない
外部の変数を使うために useEffect の依存配列を使うなら 複数回 watch を呼び出すことになるので古い watch の解除が必要
watch の返り値を受け取って useEffect が返す関数の中でサブスクリプションの解除をする