Interactive

状態管理

状態は、値の所有者と更新の原因が分かる最小の場所に置きます。サーバーの値、URL、入力途中の値、業務上の状態遷移を同じ仕組みへ集約しません。

選択基準

propsやstateから計算できる値は保存せず、レンダー中に導出します。一つのUI要素に閉じた値はuseState、複数のイベントによる関連した遷移はuseReducerで扱います。

APIから取得した値は、TanStack Queryなどのサーバー状態管理へ置きます。共有、復元、リンクが必要な絞り込みはURL search paramsを正本にします。深いツリーで共有する値のうち、多言語設定、認証状態、ログイン中のユーザー情報など全体で安定したものに限ってContextを使います。エディタのような複雑なコンポーネントで深いprops渡しを避ける場合も、Contextをそのコンポーネント内へ閉じ、アプリ全体へ広げません。

入力途中のフォーム値は、フォーム内のstateまたはプロジェクトで採用しているライブラリへ置きます。業務上の不変条件を持つ状態遷移はUIから分け、Domainの関数、Entity、ステートマシンで表します。

Reducer

Reducerは、複数のイベントで変化する関連stateを一つの遷移規則へまとめるときに使います。Discriminated Unionでactionを表し、純粋関数に保ちます。利用者の操作を原因とする通信、時刻取得、ストレージ更新はイベントハンドラで行い、購読やタイマーなど外部システムとの同期だけをEffectへ置きます。その結果をactionとして渡します。

Contextは値を深いツリーへ届ける仕組み、Reducerは状態遷移を定義する仕組みです。別の課題として選びます。Contextを使う場合はcreateContext<T | null>(null)と専用HookでProviderの不足を検出し、変更頻度の高い巨大なstateを一つにまとめません。

サーバーデータ

取得結果をローカルstateへ複製せず、query cacheを正本にします。表示用の値はquery結果から導出し、利用者が編集を始めた時点でだけフォームstateへコピーします。

query keyはserializableな値で構成し、query functionの結果を変える引数をすべて含めます。Hono RPCを使うプロジェクトでは、利用中のバージョンが提供する$path()をkeyの一部にできます。

const queryKey = [endpoint.$path(), { residentId }]

更新後のinvalidateも同じprefixを使い、関連画面の再取得を漏らしません。fetchまたはRPCの応答はres.okを確認し、HTTPエラーを成功データとしてキャッシュしません。Suspenseを採用する場合は、利用者が回復できる粒度でSuspense BoundaryとError Boundaryを配置します。

UI stateの値

UI stateは、DevTools、永続化、比較を扱いやすいプレーンでserializableな値を基本とします。振る舞いや不変条件を持つクラスインスタンスを置く場合は、参照同一性、更新方法、シリアライズ方法を明確にします。

state間の同期やイベント処理のためにEffectを使いません。外部システムとの同期だけをEffectに残し、依存配列を減らすために必要な値を隠さないようにします。