no-unnecessary-effects について
React 開発者向けに、useEffect の必要性を判断し、よくある場面での代替策を示すガイドです。外部システムとの同期に用途を絞ります。
主な機能
- 01useEffect の必要性を判断
- 02派生データをレンダー中に計算
- 03ユーザー操作をイベントハンドラーで処理
- 04外部サブスクリプションのクリーンアップを案内
活用シーン
- 01useEffect による派生状態の保存を回避
- 02クリックやフォーム送信の処理
- 03WebSocket やサードパーティウィジェットへの接続
- 04ブラウザー API の購読や DOM の計測
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cst2989/ react-tips-skill --skill no-unnecessary-effectsインストール後、Claude Code で/no-unnecessary-effectsを入力して読み込まれたか確認します。
詳細
useEffect を書く前に
useEffect を書こうとするたびに、立ち止まってこの質問に答えてください:
これは外部システムとの同期ですか?
外部システム: WebSocket、ブラウザ API(IntersectionObserver、navigator.onLine)、サードパーティライブラリ(地図 SDK、チャートウィジェット)、DOM の測定、setInterval タイマー。
外部システムではないもの: props、state、props や state から派生した値、ユーザーイベント(クリック、フォーム送信)。
答えが「いいえ」なら、その effect は書かないでください。以下の決定木を使って、適切な代替手段を見つけてください。
決定木
effect を書く前に、各ケースを順番に確認してください:
1. データを変換または派生させようとしていませんか?
インラインで計算してください。state も effect も不要です。
// NEVER do this
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(data.filter(item => item.active));
}, [data]);
// DO this
const filtered = data.filter(item => item.active);
// If genuinely expensive and not using React Compiler:
const filtered = useMemo(() => data.filter(item => item.active), [data]);
2. ユーザーイベントに応答しようとしていませんか?
ロジックはイベントハンドラーに置いてください。effect はレンダーに応答するものであり、ユーザーの操作に応答するものではありません。
// NEVER do this
useEffect(() => {
if (submitted) {
performSearch(query);
setSubmitted(false);
}
}, [submitted, query]);
// DO this
function handleSubmit(e: FormEvent) {
e.preventDefault();
performSearch(query);
}
3. props が変化したときに state をリセットしようとしていませんか?
key prop を使って、React にコンポーネントをアンマウントして新しい state で再マウントさせてください。
// NEVER do this
useEffect(() => {
setComment('');
}, [userId]);
// DO this — in the parent
<UserProfile key={userId} userId={userId} />
4. データを取得しようとしていませんか?
TanStack Query(React Query)または同様のライブラリを使用してください。どうしても useEffect を使わなければならない場合は、古いレスポンスを無視するためのクリーンアップを必ず追加してください。
// Preferred
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// If useEffect is unavoidable, always handle race conditions
useEffect(() => {
let ignore = false;
fetchUser(userId).then(data => {
if (!ignore) setUser(data);
});
return () => { ignore = true; };
}, [userId]);
5. 親コンポーネントに通知しようとしていませんか?
setState と並べて、イベントハンドラー内で親のコールバックを直接呼び出してください。React は両方の更新を 1 回のレンダーにバッチ処理します。
// NEVER do this
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
// DO this
function handleClick() {
const next = !isOn;
setIsOn(next);
onChange(next);
}
6. 複数の effect を連鎖させようとしていませんか?
そのカスケードを単一のイベントハンドラーに移動してください。レンダー中に派生できるものは派生させてください。
// NEVER do this — three effects, three render passes
useEffect(() => { setCity(''); }, [country]);
useEffect(() => { setDistrict(''); }, [city]);
useEffect(() => { setShippingCost(calculate(country, city, district)); }, [country, city, district]);
// DO this
function handleCountryChange(newCountry: string) {
setCountry(newCountry);
setCity('');
setDistrict('');
}
const shippingCost = country && city && district
? calculateShipping(country, city, district)
: 0;
7. 外部ストアを購読しようとしていませんか?
useEffect で手動でリスナーを配線する代わりに、useSyncExternalStore を使用してください。
// NEVER do this
useEffect(() => {
const handler = () => setIsOnline(navigator.onLine);
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
return () => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
};
}, []);
// DO this
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
useEffect が正しい場合
上記のどのケースにも当てはまらず、「これは外部システムですか?」という質問への答えが本当に はい であるなら、useEffect は適切なツールです。例:
- WebSocket 接続(マウント時に開き、アンマウント時に閉じる)
- サードパーティウィジェットの初期化(地図 SDK、リッチテキストエディター)
- DOM の測定(描画前は
useLayoutEffect、描画後はuseEffect) - クリーンアップを伴うブラウザ API の購読(
IntersectionObserver、ResizeObserver)
有効な effect を書くときは:
- 関数に名前を付ける と可読性が上がります:
useEffect(function connectToChat() { ... }) - 購読または接続を行う場合は 必ずクリーンアップ関数を返す
- effect が読み取る すべての依存関係を列挙する
- 視覚的なちらつきを避けるために DOM を測定する場合は
useLayoutEffectを使用する
ルール
useEffect(() => setSomething(derivedValue), [dep])は絶対に書かない — インラインで計算してください- クリック/送信/変更イベントに応答するために
useEffectを絶対に使わない - まず
keyprop を検討せずに、prop の変化で state をリセットするためにuseEffectを絶対に使わない - ある effect が state を設定し、それが別の effect をトリガーするような effect の連鎖は絶対に行わない
- 外部システムを購読する場合は必ずクリーンアップ関数を追加する
- 可読性のために必ず effect 関数に名前を付ける