LoginSignup
9
4

More than 3 years have passed since last update.

react hooksを触って、"Hooks can only be called inside the body of a function component"が出た時の話

Last updated at Posted at 2019-03-07

概要

ちゃんと<Component .../>形式で書かないとダメなようです。

経緯

先日少し作業が一段落したので、今更ながらreact-hooksを触っていました。
今まで作っていたもののうち1つを関数コンポーネント(以下FC)に置き換えようとし、さあ動確だとなったところ、

Uncaught Invariant Violation: Hooks can only be called inside the body of a function component.

というエラーが出て、上手く描画されない事態に遭遇しました。

誤解

確かに今までクラスコンポーネントで書いていた中、一部分だけを置き換えました。
また、利用側をFCに置き換えるとエラーが出ないので、「react-hooksを使うには全てFCにしないとダメなのか・・・?」と非常に焦りました。

コード例

生JSのクラスと異なりTSのクラスは色々と便利なので、FCに置き換えた際でもクラスを使うことはやめていませんでした。
(この例だと省いていますが、実際にはサポート用ロジックを色々書いていました。)


import * as React from 'react';
import * as ReactDOM from 'react-dom';

interface ChildComponentProps {
  title: string;
}

class ChildComponent { // FC形式
  public render(props: ChildComponentProps) { // このメソッドを呼び出すことで描画する
    const [title] = React.useState(props.title);
    return (
      <div>{title}</div>
    );
  }
}

class ParentComponent extends React.Component { // CC形式
  private _child = new ChildComponent();

  public render() {
    return (
      <div>
        {this._child.render({title: 'test'}}}
      </div>
    );
  }
}

ReactDOM.render(<ParentComponent />, document.getElementById('app'));

エラー原因を調査するまで、単に関数やメソッド呼び出し等で仮想DOMを返せばFCになると思っていました。

解決方法

いろいろと調べた所、下の方と同じ現象に遭遇していることに気づきました。

このとき初めて、仮想DOMを関数で返すこと≠FCという根本的な誤解に気づきました。
<Component .../>等の、Reactで取り扱う形式で記述して初めてFCとなるようです。
(確かにどこが境界なのかずっと疑問だったのと、Reactの仕組みを考えると当たり前なのですが…)

幾つか解決方法があるようですが、子のクラスインスタンスを保持する形式は変えたくなかったので、下記のようにReactNodeを返すgetterを定義し、呼び出すようにしました。


class ChildComponent {
  private _node!: (props: IChildComponentProps)=>React.ReactElement; // useMemo等に影響があるので、同一の関数にする

  public get createNode() { // このメソッドを追加
    if(!this._node) {
      this._node = (props: IChildComponentProps) => this.render(props)
    }
    return this._node;
  }

  public render(props: IChildComponentProps) {
    const [title] = React.useState(props.title);
    return (
      <div>{title}</div>
    );
  }
}

class ParentComponent extends React.Component {
  private _child = new ChildComponent();

  public render() { // JSX形式にする
    return (
      <div>
        <this._child.createNode title='test' />
      </div>
    );
  }
}


追記

上に書いたクラスを正式化したものを作りました。
FCとCCの間の子みたいな感じになっています。

9
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4