2019-06-18

エラーで学ぶReactJS

同じ轍を踏まないように今まで React で出会ったエラーと警告を 覚えてる限りこのページに纏めていきます。

info
  • ES6 で書いてます。

react (react-dom)

setState メソッド がコンポーネントから切り離されたため、updater 属性が参照できない

メッセージ
  • TypeError: Cannot read property 'updater' of undefined
対応
  • setStateのthisを固定する
OK
  • bind で this を固定する
    fetch(url).then(res => res.json()).then(this.setState.bind(this))
OK
  • アロー関数の中で setState を実行する
    fetch(url).then(res => res.json()).then(data => this.setState(data))
参考

ReactDOM.render の第二引数で指定したオブジェクトが DOM エレメントでない

メッセージ
  • Uncaught Invariant Violation: Target container is not a DOM element.
詳細
  • おそらくこれが発生するのは 取得しようとしたDOMが存在せず、 querySelectorgetElementById の返却値である null が指定されてしまうというのが多いと思います。

    ReactDOM.render(<Container />, document.querySelector('#does-not-exist'))
対応
  • 挿入する対象のDOMオブジェクトがちゃんと存在しているか確認しましょう。

特定のライフサイクルフック関数内で setState を実行したため無限ループが発生

正確には無限ループしそうになったので止めたというエラーです。 (setState の実行によってフックが呼び出されてしまうため)

メッセージ
  • Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
対応
  • ライフサイクルフック内で setState を実行しない
  • 特定の条件下でのみ setState を実行させるようにし、無限ループになることを防ぐ

何も書かれていない式が存在する

onClick={} のように式に何も書かれていないとエラーが発生します

メッセージ
  • SyntaxError: JSX attributes must only be assigned a non-empty expression
対応
  • {} 内に式を記述する
  • 例) onClick={() => console.log('clicked')}

一番外側のタグがラップされていない

隣接する要素は何らかのタグでラップする必要があります。

別の言い方をすると 式に指定する DOMオブジェクト は 一つでなければなりません。(逆にわかりにくかったらごめんなさい)

メッセージ
  • SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag
対応
  • 配列で渡すかラップする(配列の場合はkeyが必要)
NG
  • <span>a</span><span>b</span>
OK
  • [<span key="a">a</span>, <span key="b">b</span>]
OK
  • <div><span>a</span><span>b</span></div>
OK

イベントハンドラに 関数以外を指定した

メッセージ
  • Uncaught (in promise) Error: Invalid Argument Type, must only provide functions, undefined, or null.
詳細
  • イベントハンドラに 関数以外を指定するとエラーになります。
  • nullundefined は受け付けるが何もしません (逆に問題に気づきにくいのでエラーにしてほしい気持ちはある)
  • よくある間違いとして onClick={this.setState(data)} のようにしてしまうと、 (setStateの)実行結果が null として格納されるためクリックしても何も起きない、ということが起こります。
対応
  • イベントハンドラの式には関数オブジェクトを指定する {} の中で特定の関数を実行したいのであればアロー関数で覆う
NG
  • <button onClick={this.setState(data)} />
OK
  • <button onClick={() => this.setState(data)} />

undefined を コンポーネントとして描画しようとした

メッセージ
  • Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
詳細
  • 以下のように値が格納されていない変数を描画しようとすると発生します。

  • render () { let Test; return <Test /> }
  • 実際にこれが起きるケースは import に失敗していることが多いです。 具体的には以下のようなケースです。

    • export していないコンポーネントを取り込もうとしている
    • default エクスポートしていないコンポーネントを任意の名称で取り込もうとした
対応
  • export した コンポーネントを正しくインポートする

    以下のようなコンポーネントがあったとして、NG,OKを見てみましょう

    • ./mycomponent1.js
    • ./mycomponent2.js
    • export class MyComponent extends React.Component { // なにか }
    • export default MyComponent2 exptends ReactComponent { // なにか }
NG
  • import MyComponent1 from './mycomponent1'; // undefined
NG
  • import {MyComponent2} from './mycomponent2'; // undefined
OK
  • import {MyComponent1} from './mycomponent1';
OK
  • import MyComponent2 from './mycomponents2';
OK
  • import {default as MyComponent2} from './mycomponents2';

描画しようとしているオブジェクトの型が適切でない

メッセージ
  • Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {x, y, z}). If you meant to render a collection of children, use an array instead.
  • Uncaught Error: Objects are not valid as a React child (found: object with keys {xxxx}). If you meant to render a collection of children, use an array instead.
詳細
  • もし子要素のまとまりを描画したいならオブジェクトではなく 配列で渡します。
  • React が式として描画できるのは スカラ値(文字,数値,NULLなど) か ReactDOM オブジェクト、またはそれらを子とする配列です。 (他にもあったかな?) いずれにせよオブジェクト({})は描画できません。
  • 私の環境でこれが発生したのは引数の順番を変更した結果、描画用の引数で期待しないオブジェクトを受け取っていたためでした。

react-dom がない

メッセージ
  • Uncaught ReferenceError: ReactDOM is not defined
詳細
  • react-dom がない (主にインポートし忘れていると) ときに発生します
  • React 0.14 以前では React.render() を使っていたが、 それより後のバージョンでは ReactDOM.render() を使うようになったため、移行のタイミングで発生しやすいかも。
対応
  • react-dom を インポートする

    import ReactDOM from 'react-dom'
参考

エラーが発生したが開発モードでないため詳しいエラー内容を表示できない

メッセージ
詳細
  • Reactが開発モードで動かしていないのでエラーの詳細が表示されないというメッセージです
対応
  • Webpack を使っている場合は webpack.config.jsmodedevelopment を指定する。
  • 本番環境の場合は、本当のエラーを解決した後に "production" としてください。

render メソッドから 何も返却されない

メッセージ
  • Uncaught (in promise) Error: Component(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
詳細
  • render メソッドから 何も返却されない (正確に言うと undefined が返却されている)ためにエラーが発生しています。
  • 主に return が欠けている場合に発生するとメッセージには書いてありますが、 私の場合は return に何も渡していないために発生していました。
対応
  • 意図して何も描画しない場合は return だけでなく null を明示的に返却しましょう。
NG
  • (もしくは return 文がない)

    return;
OK
  • return null;

フォーム要素のコントロール状態が途中で変化した

メッセージ
  • Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
詳細
  • state で value や checked などの状態を管理している フォーム要素を controlled といい、逆に管理していない状態を uncontrolled といいます。
  • uncontrolled だったコンポーネントが途中で controlled に変わったという警告で、 この性質は一貫するべきということらしいです。 (controlled から uncontrolled もだめ)
  • そんなの変更してる覚えはないわけですが、初期値が nullundefined になった場合、 Reactは uncontrolled と判断してしまいます。
  • そして、サーバからのレスポンスやユーザの入力があると nullundefined ではなくなるので controlled になってしまうわけです。
対応
  • null にならないように 別の値に差し替える。文字列フィールドの場合は空文字 など。
NG
  • <input value={this.state.value} />
OK
  • <input value={this.state.value || ''} />
参考

「render メソッド」 や 「他コンポーネントのコンストラクタ」で state の変更があった

メッセージ
  • Warning: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
詳細
  • 「render メソッド」 や 「他コンポーネントのコンストラクタ」で state の変更があったという警告です。
  • 前者は setState によって更新を検知すると render が呼び出され、無限ループする恐れがあるためです。
  • 実際に無限ループしてしまうと Maximum update depth exceeded エラーになります。
  • 他コンポーネントのコンストラクタでの状態更新はアンチパターンのようです。 これが発生するのは setState (あるいは setState を内部的に呼び出す関数) を props として渡している場合が多いと思います。
対応
  • render メソッドやそこから呼び出される関数内で setState を呼び出さない。
  • コンストラクタ で 呼び出している setState を componentWillMount メソッドに 移動する。
    • componentWillMount メソッド は初期化に適しているそうです。
参考
info
  • 参考リンクに書いてありましたが、 BrowserHistory の push メソッドの実行も 状態変更を引き起こすようです

DOM の入れ子(ネスト)関係が不正

メッセージ
  • Warning: validateDOMNesting(...): \<tr> cannot appear as a child of \<table>. Add a \<tbody> to your code to match the DOM tree generated by the browser.
  • Warning: validateDOMNesting(...): \<a> cannot appear as a descendant of \<a>
  • Warning: validateDOMNesting(...): \<li> cannot appear as a descendant of \<li>.
  • Warning: validateDOMNesting(...): \<th> cannot appear as a child of \<thead>.
  • Warning: validateDOMNesting(...): \<td> cannot appear as a child of \<tbody>.
詳細
  • DOMの入れ子(ネスト)関係が不正。コンポーネントを入れ子にすると発生しやすいです。
    • tr は table タグの直下に置くべきではない (thead, tbodyの下に置くべき)
    • aタグ(link) の 中に a タグが含まれているべきではない。
    • li タグは ul タグの子要素になるべきだが、 li 要素の下に描画されてしまっている。
    • th タグは tr タグの子要素になるべきだが thead 要素の下に描画されてしまっている
    • td タグは tr タグの子要素になるべきだが tbody 要素の下に描画されてしまっている
対応
  • 入れ子の関係を正しくする
    • table タグ直下 にある tr タグは tbody タグで囲む
    • a タグは a タグの子孫要素に表示しない
    • li タグの親タグは ul にする (ul タグの子に li タグ以外を指定しない
    • td, th タグは tr タグで囲む

イテレーション内で描画されているDOMに key 属性が指定されていないか属性値が重複している

メッセージ
  • Warning: Each child in an array or iterator should have a unique "key" prop.
詳細
  • イテレーション内で描画されているDOMに key 属性が指定されていないか属性値が重複しているという警告です。
  • mapの中で生成する場合であっても、配列内のDOMをそのまま表示する場合であっても key は必要なので注意してください。
対応
  • 該当箇所の発見が地味に面倒くさいですが、トレースバックを追えばなんとか該当箇所はわかります
  • あとは コードに 一意な key 属性を指定するだけです。
NG
  • <div>{['a', 'b', 'c'].map((v) => <div>{v}</div>)}</div>
NG
  • <div>{ [ <div>a</div>, <div>b</div>, <div>c</div>, ] }</div>
OK
  • <div>{['a', 'b', 'c'].map((v, i) => <div key={i}>{v}</div>)}</div>
OK
  • <div>{ [ <div key="a">a</div>, <div key="b">b</div>, <div key="c">c</div>, ] }</div>

マウントされていないコンポーネントで setState や replaceState を行ったので何もしなかった

メッセージ
  • Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
  • Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
  • Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
詳細
  • メモリリークを引き起こす可能性があるので修正したほうがいい(らしい)です。
対応
  • コンポーネントがマウントされてから 更新メソッドをコールします。 多分コンポーネント間通信をしてるときに発生するんじゃないかな。

class 構文を使って getDefaultProps を定義した

メッセージ
  • Warning: getDefaultProps was defined on MyComponent, a plain JavaScript class. This is only supported for classes created using React.createClass. Use a static property to define defaultProps instead.
詳細
  • (メッセージによると)これは React.createClass を使ってコンポーネントを作るときに使われるものだそうです。
対応
  • クラス構文を使って書く場合は defaultProps プロパティを定義すればOK。
NG
  • class MyComponent extends React.Component { getDefaultProps () { return {a: 1} } // なにか }
OK
  • class MyComponent extends React.Component { // なにか } MyComponent.defaultProps = { a: 1 }
OK
参考

defaultProps プロパティをメソッドとして定義してしまった

メッセージ
  • Warning: Setting defaultProps as an instance property on MyComponent is not supported and will be ignored. Instead, define defaultProps as a static property on MyComponent.
説明
  • defaultProps プロパティを メソッドとして定義してしまった場合に発生します。 メソッドではなくオブジェクトとして定義するべきです。
対応
  • これの一つ上の Warning をご確認ください。

checked状態を変化させる手段がない

メッセージ
  • Warning: Failed prop type: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
詳細
  • checked が指定されているにもかかわらず、それを操作するための onChange ハンドラを使っていないため、チェックボックスをクリックしても変更できない(つまりReadOnly)ということを React が心配してくれてる Warning です。
対応
  • 初期のチェック状態だけを与えて、チェックボックスの ON/OFF 操作は有効にしたい場合は checked props の代わりに defaultChecked を使いましょう。

    それ以外の場合は、 onChangereadOnly props を明示します。

NG
  • // checked の状態を state で管理しているが onChange が提供されていない render () { return ( <input type="checkbox" checked={this.state.checked} /> ) }
OK
  • // checked の初期値を defaultChecked で渡しているが状態は管理していない render () { return ( <input type="checkbox" defaultChecked={this.state.checked} /> ) }
OK
  • // checked の状態を state で管理しており onChange も提供している render () { return ( <input type="checkbox" checked={this.state.checked} onChange={() => this.setState({checked: !this.state.checked})} /> ) }
OK
  • // input の checked の状態を state で管理しており、readOnly を明示している render () { return ( <div> <button onClick={() => this.setState({checked: !this.state.checked})} >{this.state.checked ? 'ON' : 'OFF'}</button> <input // この input 要る?とかいうツッコミをしてはいけない type="checkbox" style={{display: 'none'}} checked={this.state.checked} readOnly /> </div> ) }
info
  • 最後の例のように チェックボックスを別の場所から操作する場合、 readOnly props を指定すればOKです。

DOMの属性がキャメルケースで記述されていない

メッセージ
  • Warning: Invalid DOM property class. Did you mean className?
  • Warning: Invalid DOM property colspan. Did you mean colSpan?
  • Warning: Unsupported style property font-weight. Did you mean fontWeight?
詳細
  • DOMの属性は Reactが期待する記法(キャメルケース)で記述する必要があります。
対応
  • class 属性は className とする必要がある
  • td タグの colspan 属性は colSpan とする必要がある (他にもありそう)
  • CSS の プロパティ は ケバブケース (- 区切り) ではなく、キャメルケースで記述する

以下は react 関連のサードパーティライブラリ で発生した問題です

redux (react-redux)

Storeオブジェクトが正しいReducerを持っていない

メッセージ
  • Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
説明
  • Store オブジェクトが正しい Reducer オブジェクトを持っていないために発生しています。 combineReducers に Reducer を与えていない場合に発生するようです。

    (他に発生するケースが有るのかな?わかったら追記します

対応
  • combineReducers に Reducer オブジェクトを指定します。
NG
  • const reducer = combineReducers({});
OK
  • import user from './user'; // 環境によって変える const reducer = combineReducers({ user });
info
  • 新しいのに遭遇したら追記します。
  • これも追記してくれみたいな要望をコメントでくれると後で追加するかもしれません。(その場合は発生条件とかも書いてくれるとうれしい)
  • 使い方の質問やバグはライブラリの方へISSUEを出してください。(一応)

良いReactライフを! 間違いがあったらやさしく指摘ください。