はい、承知いたしました。 minified react error #185 について、原因と対策を詳細に解説する約5000語の記事を作成します。
minified react error #185 とは?原因と対策を徹底解説
はじめに
React アプリケーションを開発していると、開発環境では特に問題なく動作していたにも関わらず、本番環境にデプロイしたり、最適化されたビルド(プロダクションビルド)を使用したりした際に、画面が真っ白になったり、想定外のエラーが発生したりすることがあります。その際によく遭遇するエラーコードの一つが「minified React error #185」です。
このエラーは、本番環境向けにReactライブラリがミニファイ(圧縮・難読化)されているために表示される短いコードであり、開発環境で見られるような詳細なエラーメッセージとは異なります。そのため、「#185」だけを見ても、具体的に何が問題なのかがすぐに分かりづらいという特徴があります。しかし、この短いエラーコードの背後には、Reactのレンダリングメカニズムにおける非常に重要なルール違反が隠されています。
本記事では、この minified React error #185 が具体的に何を意味するのか、なぜ発生するのか(考えられる主要な原因)、そしてどのようにデバッグし、修正すれば良いのかについて、詳細かつ網羅的に解説します。Reactのレンダリングライフサイクルや、状態(state)およびプロパティ(props)の更新に関する基本的な考え方にも触れながら、このエラーを徹底的に理解し、適切に対処できるようになることを目指します。
なぜエラーメッセージはミニファイされるのか?
エラーメッセージがミニファイされる主な理由は、プロダクションビルドのファイルサイズを削減するためです。開発時には詳細なデバッグ情報や分かりやすいエラーメッセージが不可欠ですが、これらはビルドされたJavaScriptファイルのサイズを大きくします。本番環境では、これらの情報を削減し、ユーザーにダウンロードされるファイルサイズを最小限に抑えることがパフォーマンス向上に繋がります。Reactは、開発時には豊富なチェックと警告を提供しますが、プロダクションビルドではこれらの冗長な情報を削ぎ落とし、エラーが発生した際には短いコードで示すようになっています。
この「#185」のようなミニファイされたエラーコードは、Reactの公式ドキュメントや、React公式のミニファイエラー検索ページ などで検索することで、対応するオリジナルの開発者向けエラーメッセージを確認できます。
minified React error #185 の正体
では、minified React error #185 は具体的にどのようなエラーメッセージに対応しているのでしょうか? Reactのドキュメントで #185 を検索すると、通常、以下のようなエラーメッセージが表示されます。
Original Error Message (Development Build):
Cannot update a component (
[ComponentName]) while rendering a different component (
[OtherComponentName]).
または、少し古いバージョンのメッセージや、微妙なコンテキストの違いにより、類似のメッセージが表示されることもあります。
このメッセージを日本語に翻訳すると、以下のようになります。
日本語訳:
別のコンポーネント(
[OtherComponentName]) をレンダリングしている間に、コンポーネント(
[ComponentName]) を更新することはできません。
このメッセージが示唆しているのは、Reactのレンダリングプロセス中に、現在レンダリングされているコンポーネントとは別のコンポーネントの状態やプロパティを更新しようとしている、という状況です。
Reactのレンダリングライフサイクルと状態更新の基本
minified React error #185 の原因を理解するためには、まずReactがコンポーネントをどのようにレンダリングし、状態やプロパティの変更にどのように応答するのかという、基本的なライフサイクルとデータフローの原則を理解する必要があります。
Reactコンポーネントのライフサイクルは、大まかに以下のフェーズに分けられます(これは概念的なものであり、詳細な内部実装は異なります)。
-
Render Phase (レンダリングフェーズ):
- このフェーズでは、Reactはコンポーネントの
render
メソッド(クラスコンポーネント)または関数コンポーネント自体を実行し、次に表示されるべきUIの仮想DOMツリー(または要素ツリー)を生成します。 - このフェーズは「純粋(Pure)」である必要があります。つまり、このフェーズの実行中にコンポーネントの状態を変更したり、DOMを直接操作したり、ネットワークリクエストのような副作用(Side Effects)を実行したりしてはいけません。同じ入力(propsとstate)からは常に同じ出力(UI構造)が得られるべきです。
- Reactは、レンダリングフェーズを中断したり、複数回実行したり、並行して実行したりする可能性があります(Concurrent Modeなどで顕著)。そのため、このフェーズでの副作用は予測不能な結果を招きます。
- このフェーズでは、Reactはコンポーネントの
-
Pre-commit Phase (プレコミットフェーズ):
- 仮想DOMツリーが生成された後、ReactがDOMを更新する直前に行われるフェーズです。
getSnapshotBeforeUpdate
のような特定のライフサイクルメソッドがこのフェーズに属します。ここではDOMに関する情報を読み取ることができます。
- 仮想DOMツリーが生成された後、ReactがDOMを更新する直前に行われるフェーズです。
-
Commit Phase (コミットフェーズ):
- Reactが仮想DOMツリーの差分(Diff)を計算し、実際のブラウザのDOMを更新するフェーズです。
- DOMが更新された後、Reactは副作用を含むライフサイクルメソッド(クラスコンポーネントの
componentDidMount
やcomponentDidUpdate
)や、Hook (useEffect
,useLayoutEffect
) のコールバックを実行します。 - このフェーズは副作用を実行する主要な場所です。状態更新(
setState
や state Hook のセッター関数)は、原則としてこのフェーズで実行される副作用(イベントハンドラー、非同期処理のコールバック、useEffect
など)からトリガーされるべきです。
minified React error #185 は、まさにレンダリングフェーズ中に副作用、特に状態更新(stateやpropsの変更を引き起こす可能性のある操作)を実行しようとした結果として発生します。しかも、それは現在レンダリング中のコンポーネント自身ではなく、別の(多くの場合、親や兄弟、あるいは子の)コンポーネントに影響を与えるような更新である場合にこのエラーコードが表示されやすい傾向があります。
Reactがレンダリング中に別のコンポーネントの状態を更新されると、Reactはどのコンポーネントを次にレンダリングすべきか、レンダリングをやり直すべきかなどを判断するのが困難になります。これは、レンダリングの予測可能性を破壊し、無限ループやデータの一貫性の問題を引き起こす可能性があるため、Reactはこの操作を禁止しています。
minified React error #185 の主な原因と具体的なシナリオ
このエラーが発生する最も一般的な原因は、Reactのレンダリングフェーズ(コンポーネントの関数本体の実行中や、クラスコンポーネントの render
メソッド内)で、誤って別のコンポーネントの状態を更新するようなコードを実行してしまうことです。以下に、具体的なシナリオをいくつか挙げ、それぞれの原因と誤ったコード、そして正しい対処法を解説します。
原因 1: レンダー関数/コンポーネント本体内で直接 setState
または State Hook のセッター関数を呼び出す
これは最も基本的でよくある間違いです。コンポーネントのレンダリングロジックの中に、直接的に状態を更新する処理を書いてしまうケースです。
誤ったコード例 (関数コンポーネント):
“`javascript
import React, { useState } from ‘react’;
function ParentComponent() {
const [count, setCount] = useState(0);
// 誤った使い方: ChildComponentのレンダリング中にParentComponentの状態を更新しようとしている
// ChildComponentがレンダリングされるたびにこのロジックが実行される可能性がある
return (
Parent Count: {count}
{/ countが0より大きい場合、子コンポーネントのレンダリング中に親の状態を更新しようとする /}
if (count > 0) { // 例: 特定の条件で親を更新
setCount(count + 1); // ここでParentComponentの状態を更新
}
}} />
);
}
function ChildComponent({ onUpdateParent }) {
// ChildComponentがレンダリングされるたびにonUpdateParentが評価される
// もしonUpdateParentの内部で親の状態を更新するロジックが条件付きで実行される場合
// ChildComponentのレンダリング中にParentComponentの状態を更新することになる
onUpdateParent(); // ChildComponentのレンダリング中に親の状態を更新しようとしている!
return (
Child Component
{/ …他の子コンポーネントや要素 /}
);
}
“`
原因の説明:
上記の例では、ChildComponent
の関数本体(つまりレンダリングフェーズ)内で、親から渡された onUpdateParent
という関数を直接呼び出しています。onUpdateParent
関数は、親コンポーネントの count
ステートに基づいて親の setCount
を呼び出すロジックを含んでいます。
ChildComponent
がレンダリングされるたびに onUpdateParent()
が実行され、もし count > 0
の条件が満たされていれば、親コンポーネントである ParentComponent
の状態(count
)が更新されます。
これは、Reactが ChildComponent
のレンダリングを実行している「最中」に、別のコンポーネント(ParentComponent
)の状態を更新しようとする行為です。Reactはこれを検知すると、レンダリングの一貫性を保てなくなるため、#185 エラーをスローして処理を中断します。
誤ったコード例 (クラスコンポーネント):
“`javascript
import React from ‘react’;
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleUpdateParent = () => {
if (this.state.count > 0) {
this.setState({ count: this.state.count + 1 });
}
};
render() {
return (
Parent Count: {this.state.count}
{/ 誤った使い方: ChildComponentのレンダリング中に親の状態を更新しようとしている /}
);
}
}
class ChildComponent extends React.Component {
render() {
// ChildComponentのrenderメソッド内で親から渡された関数を直接呼び出す
// 親の状態を更新する副作用がrenderメソッド内で実行される
this.props.onUpdateParent(); // ChildComponentのレンダリング中に親の状態を更新しようとしている!
return (
<div>
<p>Child Component</p>
{/* ...他の子コンポーネントや要素 */}
</div>
);
}
}
“`
原因の説明 (クラスコンポーネント):
こちらも関数コンポーネントの例と同様です。ChildComponent
の render
メソッド内で、親から渡された this.props.onUpdateParent()
を直接呼び出しています。この呼び出しが親の setState
を引き起こし、ChildComponent
のレンダリング中に ParentComponent
の状態が更新されるという問題が発生します。
クラスコンポーネントの render
メソッドも、関数コンポーネントの本体と同様に純粋であるべきであり、副作用(特に状態更新)を含んではいけません。
正しい対処法:
状態更新のような副作用は、レンダリングフェーズではなく、副作用が許容される場所で実行する必要があります。主な場所は以下の通りです。
- イベントハンドラー内: ユーザー操作(クリック、入力など)に応じて状態を更新する場合。
- Effect Hook (
useEffect
): コンポーネントのレンダリング後に副作用を実行する場合。データのフェッチ、購読の設定、DOM操作など。依存配列を適切に設定することが重要です。 - Layout Effect Hook (
useLayoutEffect
): DOMが更新された直後、ブラウザが描画を行う直前にDOMを測定したり変更したりするような副作用を実行する場合。useEffect
よりも同期的に実行されるため、画面のちらつきを防ぐのに役立ちますが、使いすぎはパフォーマンスに影響を与える可能性があります。 - クラスコンポーネントの
componentDidMount
またはcomponentDidUpdate
: コンポーネントのマウント後や更新後に副作用を実行する場合。componentDidUpdate
を使う場合は、無限ループを防ぐために必ず更新の条件チェックを行う必要があります。
上記の誤ったコード例を修正するには、親の状態更新をトリガーするロジックを、子コンポーネントのイベントハンドラーまたはエフェクトに移動させるべきです。
修正例 (関数コンポーネント – イベントハンドラー):
もし onUpdateParent
がユーザーのアクション(例: 子コンポーネント内のボタンクリック)によってのみトリガーされるべきなら:
“`javascript
import React, { useState } from ‘react’;
function ParentComponent() {
const [count, setCount] = useState(0);
const handleUpdateParent = () => {
if (count > 0) {
setCount(count + 1);
}
};
return (
Parent Count: {count}
{/ 子コンポーネントに、親を更新する関数を渡すが、子はその関数をイベントハンドラー内で使用する /}
);
}
function ChildComponent({ onUpdateParent }) {
// 親の状態更新は、イベントハンドラー(例: ボタンクリック時)内で実行する
const handleChildButtonClick = () => {
// 何らかの処理…
onUpdateParent(); // イベントハンドラー内で親の状態を更新
};
return (
Child Component
{/ …他の子コンポーネントや要素 /}
);
}
“`
修正例 (関数コンポーネント – useEffect):
もし親の状態更新が、子コンポーネントの特定のプロパティや状態の変化に応じてレンダリング後に行われるべきなら:
“`javascript
import React, { useState, useEffect } from ‘react’;
function ParentComponent() {
const [count, setCount] = useState(0);
const [someChildTrigger, setSomeChildTrigger] = useState(false); // 子の何らかのトリガー
const handleUpdateParent = () => {
if (count > 0) {
setCount(count + 1);
}
};
return (
Parent Count: {count}
{/ 子コンポーネントに、親を更新する関数とトリガーを渡す /}
);
}
function ChildComponent({ onUpdateParent, someTrigger }) {
// 親の状態更新は、useEffectの副作用として実行する
// someTriggerが変化した時、レンダリングが完了した後に実行される
useEffect(() => {
if (someTrigger) { // 例: 子のトリガーがtrueになったら親を更新
onUpdateParent(); // useEffect内で親の状態を更新
}
}, [someTrigger, onUpdateParent]); // someTriggerかonUpdateParentが変化した場合に実行
return (
Child Component
Trigger is: {String(someTrigger)}
{/ …他の子コンポーネントや要素 /}
);
}
“`
この useEffect
の例では、ChildComponent
の someTrigger
プロパティが変更された場合に、レンダリングが完了した後に onUpdateParent
が実行されます。これにより、レンダリング中に別のコンポーネントの状態を更新する問題は回避されます。依存配列 [someTrigger, onUpdateParent]
は重要です。someTrigger
が変わったときにエフェクトを再実行するため、また onUpdateParent
がもし変更される可能性がある関数であれば、それも依存配列に含めるのがベストプラクティスです(ただし、通常親から渡される関数は useCallback
などでメモ化しないと毎回新しい関数が渡されるため注意が必要です)。
修正例 (クラスコンポーネント – componentDidUpdate):
“`javascript
import React from ‘react’;
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, someChildTrigger: false };
}
handleUpdateParent = () => {
if (this.state.count > 0) {
this.setState({ count: this.state.count + 1 });
}
};
render() {
return (
Parent Count: {this.state.count}
{/ 子コンポーネントに、親を更新する関数とトリガーを渡す /}
);
}
}
class ChildComponent extends React.Component {
// 親の状態更新は、componentDidUpdateの副作用として実行する
componentDidUpdate(prevProps) {
// someTriggerが変更されたことを確認し、条件を満たした場合にのみ親を更新
if (this.props.someTrigger !== prevProps.someTrigger && this.props.someTrigger === true) {
this.props.onUpdateParent(); // componentDidUpdate内で親の状態を更新
}
}
render() {
return (
Child Component
Trigger is: {String(this.props.someTrigger)}
{/ …他の子コンポーネントや要素 /}
);
}
}
“`
クラスコンポーネントの場合、componentDidUpdate
メソッド内でプロパティの変化をチェックし、その変化に応じた副作用を実行します。ここでも重要なのは、無限ループを防ぐために this.props.someTrigger !== prevProps.someTrigger
のような条件チェックを必ず行うことです。
原因 2: useLayoutEffect
内での同期的な状態更新が、親コンポーネントのレンダリングをトリガーする
useLayoutEffect
は useEffect
と似ていますが、DOMの更新が完了した直後、ブラウザが画面を実際に描画する前に同期的に実行される点が異なります。これはDOMの寸法を測定したり、スクロール位置を調整したりするのに役立ちますが、その同期的な性質ゆえに、そこで行われた状態更新が、親や兄弟コンポーネントのレンダリングプロセスに影響を与え、#185 エラーを引き起こす可能性があります。
特に、useLayoutEffect
内で状態を更新し、その更新が親コンポーネントの再レンダリングを引き起こし、その親コンポーネントが子のレンダリング中にさらに別のコンポーネントの更新を試みる、といった連鎖が発生した場合にこのエラーを見ることがあります。
潜在的な原因のシナリオ:
- 子コンポーネントがレンダリングされる。
- DOMが更新される。
- 子コンポーネントの
useLayoutEffect
が実行される。 useLayoutEffect
の中でsetState
が呼び出される。- この
setState
が親コンポーネントの状態を更新し、親コンポーネントの再レンダリングをトリガーする。 - 親コンポーネントのレンダリング中に、Reactが別のコンポーネントをレンダリングしようとする。
- この「別のコンポーネント」のレンダリング中に、親コンポーネントの
setState
によってトリガーされた状態更新が競合し、Reactが「別のコンポーネントをレンダリング中に、XXXコンポーネントを更新しようとしている」と判断して #185 エラーをスローする。
このシナリオは、特に複雑なコンポーネントツリーや、複数のコンポーネントが連携して状態を管理している場合に発生しやすいです。useLayoutEffect
は慎重に使用し、そこで行われる状態更新がレンダリングサイクルに与える影響を十分に理解することが重要です。
対処法:
- まず、状態更新が本当に
useLayoutEffect
でなければならないのか検討します。多くの場合、useEffect
で十分であり、非同期的に実行されるuseEffect
の方がレンダリングサイクルへの干渉が少なく安全です。 useLayoutEffect
を使用する場合は、状態更新が最小限に抑えられ、他のコンポーネントのレンダリングに予期せぬ影響を与えないように注意します。- 状態更新がトリガーする再レンダリングが、他のコンポーネントのレンダリングとどのように絡み合うかをデバッグツール(後述)で確認します。
原因 3: レガシーなライフサイクルメソッド (componentWillUpdate
, componentWillReceiveProps
) 内での状態更新
これらのライフサイクルメソッドは非同期レンダリング(Concurrent Mode)と互換性がなく、Reactの将来のバージョンでは削除される予定(または既に非推奨)です。これらのメソッドはレンダリングフェーズの直前に実行されるため、ここで setState
を呼び出すと、レンダリング中に状態が変わる可能性があり、予測不能な挙動や、#185 のようなエラーを引き起こす原因となります。
特に componentWillUpdate
は、次にレンダリングされるプロパティや状態を受け取った後に実行されるため、ここでの setState
はほぼ確実に無限ループやレンダリング中の状態更新の問題を引き起こします。
誤ったコード例 (クラスコンポーネント – 非推奨のライフサイクル):
“`javascript
import React from ‘react’;
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { derivedState: null };
}
// 非推奨で危険なライフサイクルメソッド
componentWillReceiveProps(nextProps) {
// レンダリングの直前に実行される可能性のある場所でsetStateを呼び出している
if (nextProps.someProp !== this.props.someProp) {
this.setState({ derivedState: computeDerivedState(nextProps.someProp) }); // レンダリング前/中に状態更新
}
}
// 非推奨で非常に危険なライフサイクルメソッド
// componentWillUpdate(nextProps, nextState) {
// // ここでのsetStateはほぼ常に問題を起こす
// this.setState(…);
// }
render() {
// renderメソッドは純粋であるべき
return (
Derived State: {this.state.derivedState}
{/ 他の子コンポーネント /}
);
}
}
function computeDerivedState(prop) {
// 計算コストの高い処理など
return Computed from ${prop}
;
}
“`
原因の説明:
componentWillReceiveProps
や componentWillUpdate
は、Reactが新しいプロパティや状態を受け取って再レンダリングを決定した後に実行されますが、実際の render
メソッドが実行される前です。ここで setState
を呼び出すと、Reactはまだレンダリングプロセスの途中にいると認識し、状態更新を検出してレンダリングをやり直そうとするかもしれません。これがレンダリングサイクルの途中で発生すると、他のコンポーネントとの連携によっては #185 エラーが発生します。
対処法:
これらのレガシーなライフサイクルメソッドの使用を避け、代替のモダンなアプローチに移行します。
componentWillReceiveProps
の代替としては、componentDidUpdate
またはgetSnapshotBeforeUpdate
とcomponentDidUpdate
の組み合わせ、あるいは関数コンポーネントであればuseEffect
を使用します。 props の変化に応じて状態を更新する場合は、componentDidUpdate
またはuseEffect
内でプロパティの変更を検知して状態を更新します。componentWillUpdate
の代替としては、同様にcomponentDidUpdate
を使用し、更新の条件を適切にチェックします。
修正例 (クラスコンポーネント – componentDidUpdate):
“`javascript
import React from ‘react’;
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { derivedState: null };
}
// コンポーネントがマウントされた後に初期計算を行う場合
componentDidMount() {
this.setState({ derivedState: computeDerivedState(this.props.someProp) });
}
// propsまたはstateが更新された後に、その変化に応じて状態を更新する場合
componentDidUpdate(prevProps) {
// props.somePropが変更された場合のみ状態を更新
if (this.props.someProp !== prevProps.someProp) {
this.setState({ derivedState: computeDerivedState(this.props.someProp) }); // レンダリング完了後に状態更新
}
}
render() {
return (
Derived State: {this.state.derivedState}
{/ 他の子コンポーネント /}
);
}
}
function computeDerivedState(prop) {
// 計算コストの高い処理など
return Computed from ${prop}
;
}
“`
この修正では、props の変化に応じた状態更新を componentDidUpdate
に移動しています。componentDidUpdate
はレンダリングとDOM更新が完了した後に実行されるため、レンダリングフェーズへの干渉がありません。また、if (this.props.someProp !== prevProps.someProp)
のような条件チェックを入れることで、無限ループを防いでいます。
原因 4: Context の値更新が、消費者コンポーネントのレンダリング中に別のコンポーネントの更新をトリガーする
React Context は、プロパティのバケツリレー無しにコンポーネントツリーの深くまでデータを渡す便利な方法です。しかし、Context の Provider の値が更新されると、その Context を消費している全てのコンポーネントが再レンダリングされる可能性があります。
Context の消費者コンポーネントのレンダリング中に、そのコンポーネントのロジックが別のコンポーネントの状態を更新するような副作用(例えば、useContext
で取得した値に基づいて親の状態を更新する関数を呼び出すなど)を含んでいると、#185 エラーが発生する可能性があります。これは原因 1 と類似していますが、Context の仕組みが絡むため少し複雑になります。
潜在的な原因のシナリオ:
- 親コンポーネントが Context.Provider の状態を更新する。
- Context の値が変更されたため、その Context を使用している子孫コンポーネント(コンテキスト消費者)が再レンダリングされる。
- 子孫コンポーネントの関数本体(レンダリングフェーズ)内で、Context の値や他のプロパティに基づいて、別のコンポーネント(例えば、子孫コンポーネント自身の親、あるいは Context Provider の親など)の状態を更新する関数が呼び出される。
- Reactは、子孫コンポーネントをレンダリングしている最中に別のコンポーネントの状態が更新されたことを検出し、#185 エラーをスローする。
対処法:
Context の消費者コンポーネントのレンダリングフェーズ内で、Context の値に基づいて直接的な副作用(特に状態更新)を実行しないようにします。 Context の値の変化に応じた副作用は、useEffect
または useLayoutEffect
内に移動させます。
“`javascript
import React, { useContext, useEffect } from ‘react’;
const MyContext = React.createContext(null);
function ParentComponent() {
const [contextValue, setContextValue] = useState(‘initial’);
const [parentState, setParentState] = useState(0);
// この状態更新がContextを更新する
const updateContext = (newValue) => {
setContextValue(newValue);
};
// この状態更新がParentComponentの状態を更新する
const updateParentState = (value) => {
setParentState(value);
};
return (
Parent State: {parentState}
{/ ChildComponentはContextを使用 /}
);
}
function ChildComponent() {
const { contextValue, updateParentState } = useContext(MyContext);
// 誤った使い方: Contextの値に基づいて、レンダリング中に親の状態を更新しようとする
// Contextの値が変わるたびにこのコンポーネントが再レンダリングされ、以下のロジックが実行される可能性がある
// if (contextValue === ‘updated’) {
// updateParentState(10); // レンダリング中に親の状態を更新!
// }
// 正しい使い方: Contextの値の変化に応じた副作用はuseEffect内で行う
useEffect(() => {
if (contextValue === ‘updated’) {
updateParentState(10); // useEffect内で親の状態を更新
}
}, [contextValue, updateParentState]); // contextValueかupdateParentStateが変化した場合に実行
return (
Context Value: {contextValue}
);
}
“`
この例では、ChildComponent
が useContext
で contextValue
と updateParentState
を取得しています。誤った使い方としてコメントアウトされている部分は、contextValue
が特定の値になったらレンダリング中に親の状態を更新しようとしています。これは #185 エラーの原因となります。修正例では、このロジックを useEffect
フック内に移動させ、contextValue
が変更されたときにのみレンダリング後に実行されるようにしています。
原因 5: 依存配列が不適切な useEffect
や useCallback
の使用
useEffect
や useCallback
などの Hook は、依存配列(dependency array)を指定することで、そのコールバックが実行されるタイミングを制御します。依存配列に不足がある場合や、逆に依存すべきでないものを含めてしまった場合、意図しないタイミングでコールバックが実行されたり、無限ループが発生したりすることがあります。
無限ループ(例えば、useEffect
内で状態を更新し、その状態更新が useEffect
の依存配列に含まれており、再び useEffect
が実行される、というサイクル)は、直接 #185 エラーをスローするとは限りませんが、過剰な再レンダリングを引き起こし、他のコンポーネントの状態更新ロジックとの競合を通じて、結果的に #185 エラーのようなレンダリング関連のエラーを引き起こす可能性があります。
また、親コンポーネントが子コンポーネントにコールバック関数を渡す際に useCallback
を適切に使用しないと、親が再レンダリングされるたびに新しいコールバック関数が生成され、子の useEffect
の依存配列にその関数が含まれている場合、子の useEffect
が不必要に毎回実行されることになります。その子の useEffect
が別のコンポーネントの状態を更新する副作用を含んでいると、レンダリングサイクルの途中で状態更新が発生しやすくなり、#185 エラーに繋がる可能性があります。
対処法:
useEffect
やuseLayoutEffect
の依存配列は、そのエフェクト内で使用されている全てのコンポーネントスコープの値(state, props, 関数など)を正確に含めるようにします。ESLint のeslint-plugin-react-hooks
プラグインを使用すると、依存配列の不足を検知して警告してくれます。- 関数を子のコンポーネントに渡す場合は、可能な限り
useCallback
を使用してメモ化し、不要なコールバックの再生成を防ぎます。これにより、子コンポーネントのuseEffect
が不要にトリガーされるのを防ぐことができます。 - 状態更新がトリガーするエフェクトが、別のコンポーネントの状態更新を同期的に引き起こしていないか確認します。
原因 6: 第三者ライブラリやレガシーコードとの連携
既存のライブラリや、古い React パターンで書かれたコード(例えば、非推奨のライフサイクルメソッドを多用している、DOMを直接操作しているなど)と新しい React コードを組み合わせて使用している場合に、ライブラリやレガシーコードの挙動が React のモダンなレンダリングプロセスと衝突し、予期しないタイミングで状態更新が発生して #185 エラーを引き起こすことがあります。
対処法:
- エラーが発生している箇所の周辺コードだけでなく、そのコンポーネントや親コンポーネントが使用している外部ライブラリやレガシーコンポーネントのコードも確認します。
- 特に、DOM操作を行うライブラリや、Reactの状態管理とは異なる独自のデータフローを持つライブラリを使用している場合は注意が必要です。Reactの外で発生した変更を React の状態に同期させる必要がある場合、その同期処理が適切なタイミング(イベントハンドラーや
useEffect
内)で行われているか確認します。 - 可能であれば、レガシーコードをモダンな React パターンにリファクタリングすることを検討します。
minified React error #185 のデバッグ方法
185 エラーはプロダクションビルドで発生するため、デバッグが難しい場合があります。しかし、いくつかのステップとツールを使うことで、原因箇所を特定しやすくなります。
-
開発ビルド (Development Build) でエラーを再現させる:
- これが最も重要かつ最初のステップです。 プロダクションビルドではなく、開発ビルドでアプリケーションを実行します。開発ビルドには、ミニファイされていない詳細なエラーメッセージや警告が含まれています。
- 多くの場合、開発ビルドでは #185 というコードではなく、
Cannot update a component (...) while rendering a different component (...)
という完全なエラーメッセージが表示されます。このメッセージには、問題が発生しているコンポーネントの名前 ([ComponentName]
) と、その更新がトリガーされた別のコンポーネントの名前 ([OtherComponentName]
) が含まれていることがあります。これにより、問題の根本原因を特定する大きな手がかりが得られます。 - npm/yarn を使用している場合、通常
npm start
やyarn start
は開発ビルドを実行し、npm run build
やyarn build
はプロダクションビルドを作成します。環境変数NODE_ENV=development
やNODE_ENV=production
で制御されていることが多いです。
-
エラーメッセージから元のコード箇所を特定する:
- 開発ビルドで表示されたエラーメッセージには、通常、エラーが発生したファイルのパスと行番号が含まれています。これを使用して、直接ソースコードのエラー箇所を確認します。
- ミニファイされたエラーコード (#185) の意味がどうしても知りたい場合は、React公式のミニファイエラー検索ページ にアクセスし、#185 を入力して元のメッセージを確認します。
-
ブラウザの開発者ツールを使用する:
- Console: エラーメッセージを確認するのに使います。開発ビルドであればスタックトレースも詳細です。
- Sources (ソース):
- エラーメッセージに表示されたファイルと行番号にブレークポイントを設定します。
- コードの実行をステップ実行(Step Over, Step Into, Step Out)して、どの関数呼び出しが状態更新を引き起こしているか、またそれがレンダリングフェーズのどの時点で発生しているかを追跡します。
- Call Stack(呼び出しスタック)を確認します。エラーが発生した時点までの関数呼び出しの履歴が表示されます。ここを遡ることで、どのコンポーネントのレンダリング中にエラーが発生したか、そしてその原因となった状態更新はどの関数呼び出しから来ているかを特定できます。通常、スタックトレースの中に
render
やコンポーネント関数の名前が見つかるはずです。
-
React Developer Tools を使用する:
- ブラウザの拡張機能である React Developer Tools は、Reactコンポーネントの階層、状態(state)、プロパティ(props)、Hooks の値をリアルタイムで確認できます。
- Components タブ: エラーが発生したコンポーネントを選択し、その状態やプロパティがどのように変化しているかを確認します。
- Profiler タブ: アプリケーションのレンダリングパフォーマンスを記録できます。エラーが発生する操作を行った際にプロファイリングを開始し、どのコンポーネントが再レンダリングされているか、そしてそれぞれのレンダリングにどのくらいの時間がかかっているかを確認します。これにより、無限レンダリングループや予期しないレンダリングの連鎖を特定できる場合があります。レンダリングツリーの中で、状態更新がトリガーされてレンダリングが何度も繰り返されている箇所を見つける手がかりになります。
- Hooks の状態変化を追跡することも可能です。
-
条件付きレンダリングや早期リターンを利用する:
- 疑わしいコードブロックやコンポーネントで、特定の条件が満たされた場合にのみそのコードを実行したり、コンポーネントのレンダリングをスキップしたりするロジック(例:
if (condition) { /* ... */ }
やif (condition) return null;
)を追加して、エラーが発生する条件を絞り込みます。 - 例えば、親コンポーネントの特定のプロパティや状態が変化した場合にのみ、子コンポーネントの内部で状態更新の可能性がある関数を呼び出すように変更し、どの条件がエラーを引き起こすかを確認します。
- 疑わしいコードブロックやコンポーネントで、特定の条件が満たされた場合にのみそのコードを実行したり、コンポーネントのレンダリングをスキップしたりするロジック(例:
-
原因の可能性があるコード箇所をコメントアウトまたは簡略化する:
- エラーメッセージやデバッグツールで特定した、原因の可能性が高いコンポーネントやコードブロックを一時的にコメントアウトするか、非常に単純な状態に書き換えてみます。エラーが解消されれば、コメントアウトした箇所に問題があることが分かります。
- 特に、レンダー関数/コンポーネント本体の直下にある副作用の可能性のある関数呼び出しやロジックを疑います。
minified React error #185 の修正方法とベストプラクティス
原因を特定したら、適切な方法で修正を行います。修正の原則は常に「レンダリングフェーズから副作用(特に状態更新)を取り除く」ことです。
-
状態更新をイベントハンドラーに移す:
- ユーザーの操作(クリック、入力など)に応じて状態を更新する場合は、その更新ロジックを
onClick
,onChange
,onSubmit
などのイベントハンドラー関数の中に記述します。イベントハンドラーは、ブラウザのイベントループの中で非同期的に実行されるため、レンダリングフェーズへの干渉がありません。
- ユーザーの操作(クリック、入力など)に応じて状態を更新する場合は、その更新ロジックを
-
状態更新を
useEffect
またはuseLayoutEffect
に移す:- コンポーネントのレンダリング結果やプロパティ・状態の変化に応じて副作用を実行する必要がある場合は、
useEffect
またはuseLayoutEffect
を使用します。 - ほとんどの場合は
useEffect
で十分です。DOMの更新が完了した後に非同期的に実行されます。 - DOMの寸法測定やスクロール位置調整など、DOM更新直後かつブラウザ描画直前に行う必要がある同期的な副作用の場合は
useLayoutEffect
を検討します。ただし、慎重に使用しないとパフォーマンス問題や今回のエラーのような同期的な問題を引き起こす可能性があります。 - 重要なのは依存配列を正しく設定することです。 エフェクト内で使用するコンポーネントスコープの全ての値を依存配列に含めます。これにより、エフェクトが必要な場合にのみ再実行されるようになります。
- コンポーネントのレンダリング結果やプロパティ・状態の変化に応じて副作用を実行する必要がある場合は、
-
クラスコンポーネントでは
componentDidMount
またはcomponentDidUpdate
を使用する:- クラスコンポーネントで副作用を実行する場合、マウント後に一度だけ実行するなら
componentDidMount
を使用します。 - プロパティや状態の変化に応じて実行する場合は
componentDidUpdate
を使用します。この際、必ずprevProps
やprevState
と比較して、副作用を実行すべき条件を満たしているかチェックします。これにより、無限ループや不要な副作用の実行を防ぎます。
- クラスコンポーネントで副作用を実行する場合、マウント後に一度だけ実行するなら
-
派生状態 (Derived State) の適切な扱い:
- ある状態やプロパティから別の状態を「派生」させる場合、多くの場合、その派生状態をコンポーネントの状態として持つ必要はありません。レンダー関数/コンポーネント本体内で、必要なときに計算する方がシンプルで安全です。
- 例:
fullName =
${firstName} ${lastName};
のように、firstName
とlastName
からfullName
を計算する場合、fullName
を状態として持つのではなく、レンダー関数内で常に計算します。 - 計算コストが高い場合は、
useMemo
Hook を使用して計算結果をメモ化することを検討します。 - ただし、
getDerivedStateFromProps
は特定の限られたユースケース(外部データソースとの同期など)のために設計されており、状態更新を目的としたものではありません。ここでのsetState
の使用は禁じられています。
-
コンポーネント設計の見直し:
- もし複数のコンポーネントが互いの状態に密接に依存しており、それが原因で #185 エラーが発生している場合、コンポーネントの責務や状態管理の方法を見直す必要があるかもしれません。
- 状態を引き上げて共通の親コンポーネントで管理したり、
useReducer
を使って複雑な状態遷移を管理したり、あるいは Context や外部の状態管理ライブラリ(Redux, Zustand, Recoilなど)の利用を検討したりします。ただし、Context などを使う場合も、副作用は適切な場所(useEffect
など)で行うという原則は変わりません。
-
StrictMode を活用する:
- 開発中には
<React.StrictMode>
をアプリケーションのルートに配置することを強く推奨します。StrictMode は、開発段階で潜在的な問題を早期に発見するための追加的なチェックを有効にします。 - StrictMode では、特定のライフサイクルメソッド(
componentWillMount
,componentWillReceiveProps
,componentWillUpdate
)の呼び出しを警告したり、副作用の可能性のあるコード(レンダー関数内での state セッター呼び出しなど)を検知しやすくするために、コンポーネントのレンダリング関数や一部のライフサイクルメソッドを意図的に二回実行したりします。 - これにより、純粋であるべき箇所に副作用が紛れ込んでいる場合に、開発段階でその不整合に気づきやすくなります。StrictMode で問題なく動作するコードは、プロダクションでも #185 のようなエラーが発生しにくい傾向があります。
- 開発中には
よくある誤解と注意点
- 「#185 エラーが出るのはプロダクションだけだから、開発環境で動けばOK」という考え方は危険です。 開発ビルドで詳細なエラーメッセージが出ているはずなので、それを見落としてプロダクションで初めてエラーに気づいているだけです。開発ビルドで発生した警告やエラー(特にレンダリング関連のもの)は、必ずその場で修正する習慣をつけましょう。
setState
や state Hook のセッター関数は、非同期的に実行されることが保証されているわけではありません。特にイベントハンドラーや一部のケースでは同期的に実行されることもあります。しかし、重要なのはそれがレンダリングフェーズの外部でトリガーされることです。レンダリングフェーズ内での呼び出し自体が問題となります。- 関数の渡し方にも注意が必要です。親コンポーネントが子にコールバック関数を渡し、子がその関数を
useEffect
の依存配列に含める場合、親が再レンダリングされるたびに新しい関数インスタンスが生成されると、子のuseEffect
が不必要に毎回実行されてしまいます。これを防ぐために、useCallback
を使用して関数をメモ化します。
“`javascript
function ParentComponent() {
const [state, setState] = useState(0);
// stateが変わるたびに新しい関数が生成されてしまう可能性がある
// const updateState = () => { setState(state + 1) };
// useCallbackでメモ化することで、stateが変わった時だけ新しい関数が生成される
const updateState = useCallback(() => {
setState(prev => prev + 1); // 関数型更新を使用するのがより安全
}, []); // 空の依存配列は初回レンダリング時のみ関数を生成(stateに依存しない場合)
// stateに依存する場合は [state] を含めるが、useCallbackを使う意味が薄れる場合もある
// 関数型更新 (prev => prev + 1) を使えば依存配列から state を除外できることが多い
return
}
function ChildComponent({ onUpdate }) {
useEffect(() => {
// onUpdate が依存配列に含まれている
// 親から渡される関数が useCallback でメモ化されていないと
// 親が再レンダリングされるたびにこの effect が実行されてしまう可能性がある
console.log(‘Effect triggered’);
// onUpdate(); // もしここで呼び出すと、無限ループや #185 の原因になりうる
}, [onUpdate]); // onUpdate が変化した場合に effect を再実行
return ;
}
“`
この例のように、子コンポーネントの useEffect
が親から渡された関数に依存している場合、親の関数を useCallback
でメモ化することで、子の useEffect
の不必要な実行を防ぎ、関連する潜在的な問題を回避できます。
まとめ
minified React error #185 は、プロダクションビルドで表示されるエラーコードであり、「別のコンポーネントをレンダリングしている間に、コンポーネントを更新することはできません」という意味です。このエラーは、Reactのレンダリングフェーズ中に副作用、特に状態更新を実行しようとした結果として発生します。これは、ReactがUIの予測可能性とパフォーマンスを保証するために設けている「レンダー関数/コンポーネント本体は純粋であるべき」という重要なルールに違反しています。
エラーの主な原因は以下の通りです。
- レンダー関数/コンポーネント本体内で直接
setState
や State Hook のセッター関数を呼び出す。 useLayoutEffect
内での同期的な状態更新が、親コンポーネントのレンダリングを予期せずトリガーする。- 非推奨のライフサイクルメソッド(
componentWillUpdate
,componentWillReceiveProps
)内での状態更新。 - Context の値更新が、消費者コンポーネントのレンダリング中に別のコンポーネントの更新をトリガーするような副作用をConsumerコンポーネントの本体に含んでしまっている。
useEffect
やuseCallback
の依存配列が不適切であることによる、予期しない副作用の実行。- 第三者ライブラリやレガシーコードとの連携における副作用の不適切な管理。
このエラーをデバッグする最も効果的な方法は、まずアプリケーションを開発ビルドで実行し、詳細なエラーメッセージを確認することです。ブラウザの開発者ツール(Console, Sources, Call Stack)や React Developer Tools(Components, Profiler)を活用して、エラーが発生している正確なコード箇所と、その時点でのコンポーネントの状態や呼び出し履歴を特定します。
エラーの修正方法は、原因となった副作用(状態更新)をレンダリングフェーズから移動させることです。具体的には、イベントハンドラー、useEffect
/ useLayoutEffect
、またはクラスコンポーネントの componentDidMount
/ componentDidUpdate
(条件チェック付き)内で状態更新を行うようにコードを修正します。また、StrictMode を活用することで、開発段階で潜在的な問題を早期に発見できます。
minified React error #185 は、Reactのコンポーネントライフサイクルとデータフローの基本原則を理解していれば回避できるエラーです。本記事で解説した原因と対策、そしてデバッグ方法を参考に、クリーンで安定したReactアプリケーション開発を目指してください。
付録: minified React error #185 に関連するその他の考慮事項
パフォーマンスへの影響
レンダー中に状態を更新することは、エラーを引き起こすだけでなく、パフォーマンスにも悪影響を及ぼします。Reactは状態が更新されるとコンポーネントを再レンダリングしようとしますが、もしレンダリングの途中で状態が再度更新されると、Reactはレンダリングを中断してやり直す必要があるかもしれません。このような状況が連鎖的に発生すると、UIの応答性が低下したり、ブラウザがフリーズしたりする原因となります。#185 エラーは、このような非効率的で危険な状態更新パターンを React が強制的に停止させている結果とも言えます。
将来の React (Concurrent Mode) との関連
React の Concurrent Mode(並行モード)は、レンダリングを中断可能にし、タスクに優先順位をつけることで、よりスムーズなUI更新を実現することを目指しています。Concurrent Mode では、レンダリングフェーズが複数回中断・再開されたり、並行して実行されたりする可能性があります。このような環境では、「レンダー関数は純粋であるべき」という原則がさらに重要になります。レンダリングフェーズで副作用を実行するコードは、Concurrent Mode の挙動と衝突し、さらに予測不能な結果を招く可能性が高まります。#185 エラーが指摘している問題は、Concurrent Mode を含む将来の React においても基本的なアンチパターンであり続けます。
テストの重要性
この種のエラーは、コンポーネント間の複雑な相互作用や、特定の状態遷移の組み合わせによってのみ発生することがあります。単体テストや統合テストを十分に実施することで、このようなエッジケースでの問題を早期に発見し、プロダクション環境でのエラー発生リスクを減らすことができます。特に、コンポーネントのプロパティや状態が変化した際の振る舞い、およびそれに応じた副作用(useEffect
など)の実行が意図通りに行われるかをテストすることは重要です。
結論
minified React error #185 は、Reactアプリケーション開発において遭遇しうる一般的なエラーの一つです。このエラーは、プロダクションビルドのファイルサイズを小さくするためにミニファイされたエラーコードであり、その裏には「レンダリング中に別のコンポーネントの状態やプロパティを更新しようとした」という、Reactのレンダリングルールに対する重大な違反が隠されています。
本記事で詳細に解説したように、このエラーの根本原因は、コンポーネントの関数本体やクラスコンポーネントの render
メソッドといったレンダリングフェーズ内で、状態更新のような副作用を実行してしまうことにあります。これは、ReactがUIの予測可能性、一貫性、そしてパフォーマンスを維持するために必須としている「レンダーフェーズの純粋性」を損なう行為です。
エラーを解決するための鍵は、以下の原則を徹底することです。
- 副作用はレンダリングフェーズの外で実行する: イベントハンドラー、
useEffect
/useLayoutEffect
、またはクラスコンポーネントのcomponentDidMount
/componentDidUpdate
を使用します。 useEffect
/useLayoutEffect
およびcomponentDidUpdate
では、無限ループを防ぐために適切な条件チェックや依存配列の設定を行う: 副作用が必要な場合にのみ実行されるように制御します。- 開発中は StrictMode を有効にし、開発ビルドでデバッグを行う: 詳細なエラーメッセージと警告を活用して、問題を早期に発見・特定します。
- 派生状態の扱いに注意する: レンダリング中に計算可能な値は状態として持たず、必要に応じて計算します。
- Context や外部ライブラリとの連携における副作用のタイミングに注意する: これらの変更に対するReactの状態更新も、適切な場所で行われる必要があります。
minified React error #185 を理解し、適切に対処できるようになることは、Reactアプリケーションの品質と安定性を高める上で非常に重要です。このエラーメッセージに遭遇した際は、焦らず、開発ビルドに戻して詳細なエラーメッセージを確認し、スタックトレースやReact Developer Tools を活用して原因箇所を特定してください。そして、本記事で紹介した修正方法とベストプラクティスを参考に、コードを修正してください。これらの知識と習慣を身につけることで、より堅牢で保守しやすいReactアプリケーションを構築できるようになるでしょう。