概要
機会があって React をいじっていたときのことです。
「setState()
したのに state
が更新されていない!なぜ!?ホワイ!?」
という出来事がありました。そのときの失敗談を共有したいと思います。
詳細
サンプルコードです。
注目してほしいのは componentDidMount
のところです。setState
を呼んだ直後に state
をコンソールに出してみたところ値が変わっていません。(この時点で、「ん?それの何がおかしいの?」 という賢明な識者の方は特にこの記事から得るものは何もないと思いますのでブラウザバックでおkです)
class MessageBox extends React.Component {
constructor() {
super()
console.log("Called constructor")
//初期値
this.state = {
message: "なんでもしますから"
}
}
componentDidMount() {
console.log("Called componentDidMount")
//state を更新(した気になる...がしかし)
this.setState({message: "なんでもするとは言ってない"})
console.log("Called setState in componentDidMount")
//state が変わってない!!
console.log("state.message => " + this.state.message) // => "なんでもしますから"
}
render() {
console.log("Called render")
//ここでようやく setState で予約した state の変更を確認
console.log("state.message => " + this.state.message) //=> "なんでもするとは言ってない"
return (<p>message: {this.state.message}</p>)
}
}
React.render(<MessageBox />, document.getElementById('app'));
componentDidMount
で setState
を呼ぶという状況があったんですが、その直後に state
から値を取得したら古いもののままでした。componentDidMount
内ですぐ更新後の値を使いたい、という場面があったためちょっとハマりました。
答え
公式ドキュメントに載ってました
https://reactjs.org/docs/react-component.html#setstate
Think of setState() as a request rather than an immediate command to update the component.
setState()
は即コンポーネントの更新をするコマンドではない ことがはっきり書かれています。setState()
を呼んだ段階では state
の変更リクエストを出したにすぎず、その時点で即時反映されるわけではないんですね。 仕様通りやん\(^o^)/ ここでは componentDidMount
の処理がまだ続いているため再レンダリングは行われていません。なので state
の変更リクエストはまだ処理されていないということなのでしょう。
言われてみればそうしないと、componentDidMount
内で setState
を何度も呼んだ場合は、その度に再レンダリングが行われてしまいそうです。なるほど、そう考えると理に適ってます。でも名前がちょっと紛らわしいのでいっそのこと setChangeStateRequest()
みたいな名前でもよかったようなw
console.log
での確認
console.log
を節目に仕込んでみましたので出力からも挙動を確認できます。
このサンプルコードを実行すると以下のような状態になります。(見やすくするため、メソッドごとに改行は入れてます)
"Called constructor"
"Called render"
"state.message => なんでもしますから"
"Called componentDidMount"
"Called setState in componentDidMount"
"state.message => なんでもしますから" //まだ setState する前と値が変わってない!!
"Called render"
"state.message => なんでもするとは言ってない" //ここでようやく setState が反映される
今日の教訓
setState()
しても state
の更新を予約するだけで、すぐには state
は更新されません。render()
が呼ばれる前に更新後の値をすぐ使いたい、という場合はご注意ください。(あまりないとはおもいますが)
おまけ
検証に使ったコードを CodePen に残してみました。興味のある方はお立ち寄りください。
See the Pen React at CodePen by TOMMY a.k.a. Jpsern, Jps. (@jpsern) on CodePen.