Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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前に呼び出されるので重宝します。

onomimono
平日PM, 週末開発。 Twitter:@onomimono
https://3people.biz/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした