このエラーが発生する原因ブラウザのコンソールを開くと、次のエラーが表示されます:
Warning: Cannot update a component from inside the function body of a different component.
Reactは、コンポーネントAのレンダーフェーズ中に、コンポーネントBの状態更新がトリガーされたことを検出しました。レンダーは純粋でなければなりません。副作用や状態の変更は許可されていません。レンダー中のコンポーネント間の状態更新は禁止されています。 ほとんどのケースで見られる3つのパターンがあります:
- 子コンポーネントがレンダーボディ内で、propsとして渡された親の状態セッターを直接呼び出している- レンダー中にReduxまたはContextのアクションがディスパッチされている-
useEffectの外で、レンダー中に同期的にコールバックを発火させるカスタムフック## バグを再現するこの最小限の例で、確実に警告を再現できます:
// Parent.jsx
function Parent() {
const [status, setStatus] = useState('idle');
return (
<div>
<p>Status: {status}</p>
<Child onReady={() => setStatus('ready')} />
</div>
);
}
// Child.jsx — 壊れたコード
function Child({ onReady }) {
onReady(); // <-- レンダー中に呼び出され、Parentの状態を更新する
return <div>Child content</div>
}
Childがレンダーされると、即座にonReady()を呼び出します。その呼び出しがParent内のsetStatusを実行します。Reactはこれを検知して警告を発します。
クイック修正:呼び出しをuseEffectに移動する呼び出しをuseEffectでラップします。これはレンダー後に実行され、レンダー中には実行されません:
// Child.jsx — 修正済み
import { useEffect } from 'react';
function Child({ onReady }) {
useEffect(() => {
onReady();
}, []); // 最初のレンダー後に一度だけ実行
return <div>Child content</div>
}
副作用がレンダーフェーズの外に移動しました。onReadyはマウント後に発火します。これが本来あるべき場所です。
依存配列に注意してください。 onReadyが親のレンダーごとに再生成される場合、このエフェクトはループで実行されます。親でuseCallbackを使って参照を安定させましょう:
// Parent.jsx
const handleReady = useCallback(() => {
setStatus('ready');
}, []);
<Child onReady={handleReady} />
レンダー中のRedux / Contextディスパッチの修正コンポーネントボディ内でのディスパッチも同じ間違いであり、構文が異なるだけです:
// 壊れたコード
function NotificationBadge({ hasNew }) {
if (hasNew) {
dispatch(markAsSeen()); // <-- レンダー中に実行!
}
return <span>{hasNew ? 'New' : ''}</span>
}
// 修正済み
function NotificationBadge({ hasNew }) {
useEffect(() => {
if (hasNew) {
dispatch(markAsSeen());
}
}, [hasNew, dispatch]);
return <span>{hasNew ? 'New' : ''}</span>
}
レンダー中に親へ通知するコールバックの修正より微妙なパターン:子コンポーネントがレンダー中にpropのコールバックを条件付きで呼び出し、内部の状態を親に通知しようとするケースです。一見問題なさそうに見えますが、そうではありません:
// 壊れたコード — Dropdownのレンダー中にコールバックが親の状態を更新する
function Dropdown({ isOpen, onStateChange }) {
if (isOpen) {
onStateChange('open'); // 別のコンポーネントの状態更新をトリガーする
}
return <div>...</div>
}
// 修正済み
function Dropdown({ isOpen, onStateChange }) {
useEffect(() => {
onStateChange(isOpen ? 'open' : 'closed');
}, [isOpen, onStateChange]);
return <div>...</div>
}
知っておくべきこと:レンダー中にコンポーネント自身の状態を更新するのは別の話です。ガード条件を使えば、Reactは明示的にこれを許可しています:
// OK — ガード条件付きで自身の状態を更新する(getDerivedStateFromPropsのフック版)
function Dropdown({ isOpen }) {
const [open, setOpen] = useState(false);
if (isOpen !== open) {
setOpen(isOpen); // Reactは即座に再レンダーし、中間コミットをスキップする
}
return <div>...</div>
}
Reactは中間状態をコミットせずに、コンポーネントを即座に再レンダーします。レンダー中に禁止されているのは、他のコンポーネントの状態を更新することだけです。
レンダー中にコールバックを発火するカスタムフックの修正カスタムフックでも同じ問題が隠れていることがあります。フックがuseEffectの外で、フックボディ内でコールバックを呼び出すと、同じ警告が発生します。ただし、発見するのが難しくなります:
// 壊れたカスタムフック
function useDataLoader(onLoad) {
const data = fetchFromCache();
if (data) {
onLoad(data); // レンダー中に発火!
}
return data;
}
// 修正済みカスタムフック
function useDataLoader(onLoad) {
const data = fetchFromCache();
useEffect(() => {
if (data) {
onLoad(data);
}
}, [data, onLoad]);
return data;
}

