脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

ReactのcomponentWillReceiveProps内ではまだpropsは反映されていない

とてつもなくはまったのでメモ。

componentWillReceiveProps

コンポーネントのプロパティ(props)は親コンポーネントにより任意のタイミングで変更されます。
その場合、componentWillReceivePropsが呼ばれるので、そこで新しいpropsの値を参照して、
それを基にコンポーネントの状態(state)を変更したり、その他の処理を行うことが可能です。

(入門React第3章より)

  • こんな風に書かれてあるもんなので、てっきりcomponentWillReceiveProps内では送られたpropsが参照できると思っていました。

しかし、実際には、引数として指定してやらないと参照は出来ないようです。

例えば、以下のようにエンターキーを押すとテキストボックスに入力された文字を基に再描画が発生する親コンポーネントがあったとします。

//parent
var Parent = React.createClass(
    {
        getInitialState() {
            return {
                value: "old value",
            };
        },
        sendCommand(e) {
            if(e.keyCode == 13){
                var new_value = 'new value';
                this.setState({value: new_value});
            }
        },
        render: function() {
            return (
              <div>
                <Child value={this.state.value} />
                <input type="text"onKeyDown={this.sendCommand} />
              </div>
            );
        }
    }
);

Enterキーが押される度にsetStateが起きる、つまり子コンポーネントに新しいpropsが渡される
ということは子コンポーネントのcomponentWillReceivePropsが呼び出される。
ここまではよかった。

が、このメソッド内部のpropsは、古いまま(前回のコンポーネント作成時のままである)

//child
var Child = React.createClass(
    {
        componentWillReceiveProps() {
            console.log(this.props.value); // => 'old value'
        },
        ....
    } 
);

なので、入門Reactの通りこの内部で送られたpropsを使うには、以下のように引数としてpropsを受け取らなければいけない

//child
var Child = React.createClass(
    {
        componentWillReceiveProps(nextProps) {
            console.log(nextProps.value);  // ===> 'new value'
            console.log(this.props.value); // ===> 'old value'
        },
        ....
    } 
);


setStateは即時反映されない問題

この問題はさらにややこしくしていたのが、setStateは即時反映(set)されるわけではないという謎の仕様
例えば上の親コンポーネントで例を示すと

//parent
var Parent = React.createClass(
    {
        getInitialState() {
            return {
                value: "old value",
            };
        },
        sendCommand(e) {
        ....
                var new_value = 'new value';
                this.setState({value: new_value});
                console.log(this.state.value) // ==> old valueのまま! 
        ....
        },
        .....
    }
);

render内では反映された変更済みの値が参照できますが、this.setStateした時点では実際にはまだ更新はされていないようで、
その次の行とかで参照しても昔の値になってしまいます。
パフォーマンスなのか他の理由でこうしているのかはわかりませんが、これは結構否定意見が多かったらしく、
対応策としてthis.setStateの引数に関数を入れられるという対応がとられました。