LoginSignup
7
7

More than 5 years have passed since last update.

React-routerのBrowerHistoryを、constructor内で使うと、Warning: setState(...):が発生する謎を解いた

Last updated at Posted at 2016-11-21

Reactなんだか良くわからんWarning多すぎ問題

以下は /user(user.js) にアクセスした時に
currentUserがnullであれば(=signinしていなければ)
/sign-in (SIGN_IN_PATH) に遷移しなさいという処理。

加えて、user/hogeページ(this.props.children)にアクセスした時も、
currentUserがnullじゃないかどうかを判断しましょうね、というコード。
(こちらは今回は蛇足です)

user.js
class User extends Component {
  constructor(props) {
    super(props);
    this.state = {currentUser: null}
    //問題となる部分
    if (!this.props.currentUser){
      return browserHistory.push(CommonConstants.SIGN_IN_PATH);
    }
  }

....
  // /user であれば renderMyAccount を
  // /user/hoge であればそれをレンダリングする
  renderContent() {
    if (this.props.children) {
      return this.props.children;
    } else {
      return this.renderMyAccount();
    }
  }

  renderMyAccount() {
    const {t} = this.props;
    const user = this.state.currentUser;
    if (!user) {return}
    return (
      <div className='col-md-8 col-md-offset-2'>
        アカウントページをずらっと出力
      </div>
    )
  }

  render() {
    return (
      <div className='row'>
        {this.renderContent()}
      </div>
    );
  }
}

しかしエラーがでる。

Warning: setState(...): 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をアップデートしていないか?
②他のコンポーネントのconstructorの中で(略

①render()内でstateをアップデートするのはなぜダメなのか?

renderの中でsetStateを呼んではいけない理由

render時にstateが更新される=>stateが更新されるとrenderが実行される=>render時にstateが更新される=>stateが・・・・ということになり無限にrenderが実行されてしまっていた。

なるほど、、、しかし今回はこのケースではないです。

②他のコンポーネントのconstructorの中でstateをアップデートするのはなぜダメなのか?

What will happen if I use setState() function in constructor of a Class in ReactJS or React Native?
Ability to call setState in the constructor

簡単に言えば、非同期通信だかららしい。

どういうことかといえば、stateは本来一つのコンポーネントに依存しているべきであって、受け渡したり、複数のコンポーネントで同時に使われるべきではないということ。
非同期通信が可能故に、2つのコンポーネントでstateが同時進行的に変わるのは好ましくないということでした。

でも今回はstateの更新なんてしてないぞ??

問題は今回のソースコードのconstructor内で、setState()を使っていないということでした。

原因はBrowserHistoryのpushメソッドにありました。
React-routerの公式ドキュメント
History関数の公式ドキュメント

Navigation

history objects may be used programmatically change the current
location using the following methods:

history.push(path, [state])
history.replace(path, [state])
history.go(n)
history.goBack()
history.goForward()
history.canGo(n) (only in createMemoryHistory)

というわけで、push,replaceどちらを使っても、stateを受け渡すことになってしまい、それがエラー文中のupdateに該当しているということでした。
(間違っていたら是非指摘していただきたいです。)

解決策

componentWillMountを使う
こちらを参照
こちらも参照
これだったら確実にrender前に実行できます。

user.js
class User extends Component {
  constructor(props) {
    super(props);
    this.state = {currentUser: null}
    //ここでは一度返す
    if (!this.props.currentUser) {return}
    UserAction.getUser((user) => {
      this.setState({currentUser: user});
    });

  //ここで呼び出す。
  componentWillMount() {
    if (!this.props.currentUser) {
      return browserHistory.push(CommonConstants.SIGN_IN_PATH);
    }
  }
}

....

  renderContent() {
    if (this.props.children) {
      return this.props.children;
    } else {
      return this.renderMyAccount();
    }
  }

componentWillMountはrender前に呼び出されるので重宝します。

7
7
0

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
7
7