エラーで学ぶReactJS
2018-09-25

目次

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

備考

ES6 で書いてます。

react (react-dom)

Error (react)

メッセージ
TypeError: Cannot read property 'updater' of undefined
説明
setState メソッド がコンポーネントから切り離されたため、updater 属性が参照できないというエラーです。
対応
  • bind で this を固定する
    • 例) fetch(url).then(res => res.json()).then(this.setState.bind(this))
  • アロー関数の中で setState を実行する
    • 例) fetch(url).then(res => res.json()).then(data => this.setState(data))
参考

--

メッセージ
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 の実行によってフックが呼び出されてしまうため)
対応
  • ライフサイクルフック内で setState を実行しない
  • 特定の条件下でのみ setState を実行させるようにし、無限ループになることを防ぐ

--

メッセージ
SyntaxError: JSX attributes must only be assigned a non-empty expression
説明

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

例) onClick={}

対応

{} 内に式を記述する

例) onClick={() => console.log('clicked')}

--

メッセージ
SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag
説明

隣接する要素は何らかのタグでラップする必要があります。 別の言い方をすると 式に指定する DOMオブジェクト は 一つでなければなりません。(逆にわかりにくかったらごめんなさい)

NG: <span>a</span><span>b</span>

対応

ラップするか、配列で渡す

  • OK: <div><span>a</span><span>b</span></div>
  • OK: [<span>a</span>, <span>b</span>]

--

メッセージ
Uncaught (in promise) Error: Invalid Argument Type, must only provide functions, undefined, or null.
説明

イベントハンドラに 関数以外を指定するとエラーになります。

備考

nullundefined は受け付けるが何もしません (逆に問題に気づきにくいのでエラーにしてほしい気持ちはある)

よくある間違いとして

onClick={this.setState(data)} のようにしてしまうと、 (setStateの)実行結果が null として格納されるためクリックしても何も起きない、ということが起こります。

対応

イベントハンドラの式には関数オブジェクトを指定する

{} の中で特定の関数を実行したいのであれば アロー関数で覆う

  • NG: onClick={this.setState(data)}
  • OK: onClick={() => this.setState(data)}

--

メッセージ
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.
説明

undefined を コンポーネントとして描画しようとすると発生します。

render () {
  let Test;
  return <Test />
}

上記で再現できますが、実際にこれが起きるケースは import に失敗していることが多いです。

具体的には以下のようなケースです。

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

export した コンポーネントを正しくインポートする

これが ./mycomponent1.js

export class MyComponent extends React.Component {
  // なにか
}

これが ./mycomponent2.js だとすると

export default MyComponent2 exptends ReactComponent {
  // なにか
}

使用側では 次のようにする

// NG
import MyComponent1 from './mycomponent1' // undefined
// OK
import {MyComponent1} from './mycomponent1'
// NG
import {MyComponent2} from './mycomponent2' // undefined
// 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 オブジェクト、またはそれらを子とする配列です。 (他にもあったかな?) いずれにせよオブジェクト({})は描画できません。

私の環境でこれが発生したのは 引数の順番を変更した結果、描画用の引数で期待しないオブジェクトを受け取っていたためでした。

--

メッセージ
Uncaught ReferenceError: ReactDOM is not defined
説明

react-dom がない (主にインポートし忘れていると) ときに発生

React 0.14 以前では React.render() を使っていたが、 それより後のバージョンでは ReactDOM.render() を使うようになったため、移行のタイミングで発生しやすいかも。

対応

react-dom を インポートする

import ReactDOM from 'react-dom'

参考
Uncaught ReferenceError: ReactDOM is not defined

--

メッセージ
Minified React error #152; visit https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=y for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
説明
エラーが発生したが 開発モードでないので、詳しいエラー内容については表示されません。
対応

Webpack を使っている場合は webpack.config.jsmodedevelopment を指定する。

警告

本番環境の場合は、本当のエラーを解決した後に mode: "production" としてください。

Warning (react)

メッセージ
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 || ''} />
参考
Reactのuncontrolled input warningで困った時に確認するべきたった1つのこと | I am mitsuruog

--

メッセージ
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 として渡している場合が多いと思います。

備考

参考リンクに書いてありましたが、 BrowserHistory の push メソッドの実行も 状態変更を引き起こすようです

対応
  • render メソッドやそこから呼び出される関数内で setState を呼び出さない。
  • コンストラクタ で 呼び出している setState を componentWillMount メソッドに 移動する。
    • componentWillMount メソッド は 初期化に適しているそうです。
参考

--

メッセージ
  • 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>.
説明

DOM の入れ子(ネスト)関係が不正。コンポーネントを入れ子にすると発生しやすい。

  • tr は table タグの直下に置くべきではない。
  • aタグ(link) の 中に a タグが含まれているべきではない。
  • li タグは ul タグの子要素になるべきだが、 li 要素の下に描画されてしまっている。
対応

入れ子の関係を正しくする

  • table タグ直下 にある tr タグは tbody タグで囲む
  • a タグは a タグの子孫要素に表示しない
  • li タグの親タグは ul にする (ul タグの子に li タグ以外を指定しない

--

メッセージ
Warning: Each child in an array or iterator should have a unique "key" prop.
説明

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

map の中で生成する場合であっても、配列内のDOMをそのまま表示する場合であっても key は必要なので注意してください。

対応

該当箇所の発見が地味に面倒くさいですが、トレースバックを追えばなんとかわかります

react-key-warning-traceback.png

あとは コードに 一意な key 属性を指定するだけです。

NG
<div>{['a', 'b', 'c'].map((v) => <div>{v}</div>)}</div>
OK
<div>{['a', 'b', 'c'].map((v, i) => <div key={i}>{v}</div>)}</div>
NG
<div>{
  [
    <div>a</div>,
    <div>b</div>,
    <div>c</div>,
  ]
}</div>
OK
<div>{
  [
    <div key="a">a</div>,
    <div key="b">b</div>,
    <div key="c">c</div>,
  ]
}</div>

イテレーション内で 描画している コンポーネントには一意な key 属性を指定する

--

メッセージ
  • 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.
説明

マウントされていないコンポーネントで setState や replaceState を行ったので何もしなかったよ。という警告です。

メモリリークを引き起こす可能性があるので修正したほうがいい(らしい)です。

対応
コンポーネントがマウントされてから 更新メソッドをコールします。 多分コンポーネント間通信をしてるときに発生するんじゃないかな。

--

メッセージ
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.
説明

class 構文を使って getDefaultProps を定義すると発生するようです。

(メッセージによると)これは React.createClass を使ってコンポーネントを作るときに使われるもので、 クラス構文を使って書く場合は defaultProps プロパティ を定義すればいいそうです。

対応
NG
class MyComponent extends React.Component {
  getDefaultProps () {
    return {a: 1}
  }
  // なにか
}
OK
class MyComponent extends React.Component {
  // なにか
}
MyComponent.defaultProps = {
  a: 1
}
OK

class-properties が使える場合 (babel-plugin-transform-class-properties)

TypeScript? なんですかそれは?

class MyComponent extends React.Component {
  static defaultProps = {
    a: 1
  }
}
参考

--

メッセージ
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 をご確認ください。

--

メッセージ
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>
  )
}

最後の例のように チェックボックスを別の場所から操作する場合、 readOnly props を指定すればOKです。

--

メッセージ
  • 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 関連のサードパーティライブラリ で発生した問題です

react-bootstrap

bootstrap3 をうまく使うためのラッパーです。bs4にも対応中らしい。

備考

案件の都合で使ってるんですが、使ってる感じそんなに使いやすくないので bs4使ってる人は reactstrap/reactstrap とかどうでしょう?(使ったことないけど)

Warning (react-bootstrap)

メッセージ
Warning: Failed prop type: The prop id is required to make Dropdown accessible for users of assistive technologies such as screen readers.
説明
Dropdown コンポーネントの id 属性が不足しているときに発生する警告です。
参考
javascript - Failed propType: The prop id is required to make Dropdown accessible for users of assistive technologies such as screen readers - Stack Overflow
対応
  • DropDownButton に id 属性を設定するだけ
  • ただし、これは HTML属性にもなるためHTML中に複数出現する場合は一位になるキーを含めたほうが良い

--

メッセージ
Warning: Failed prop type: The prop title is marked as required in DropdownButton, but its value is undefined.
説明
DropdownButton コンポーネントに title props を指定していないときに発生する警告です
対応
title属性を指定するだけです。 ないときは空文字 '' でOK。
<DropdownButton title=''><DropdownButton>
参考
Dropdown Title · Issue #2592 · react-bootstrap/react-bootstrap

--

メッセージ
  • Warning: React does not recognize the activeHref prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase activehref instead. If you accidentally passed it from a parent component, remove it from the DOM element.
  • Warning: React does not recognize the activeKey prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase activekey instead. If you accidentally passed it from a parent component, remove it from the DOM element.
説明
Nav コンポーネントの 子に NavItem 以外のコンポーネントが指定されていると発生します。
対応
  • Nav コンポーネントの 子に NavItem コンポーネントを指定する
  • Nav コンポーネント以外を親に指定する

とはいっても、要件的に子が NavDropdown じゃないとできないことがあったりしてイラッとします。

参考

備考

  • 新しいのに遭遇したら追記します。
  • これも追記してくれみたいな要望をコメントでくれると後で追加するかもしれません。(その場合は発生条件とかも書いてくれるとうれしい)
  • 使い方の質問やバグはライブラリの方へISSUEを出してください。(一応)

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