React.jsでイベントを扱っていたところ、引っかかったところが2箇所ほどありました。
非同期なsetState
とイベントオブジェクト
前のstate
やprops
を見て、その値をもとにsetState
をかけたい場合、setState
に関数を渡して、その中で新しいstate
を作ることが推奨されています。ただし、このコールバックは非同期で実行されます。
handleChange = (e) => {
this.setState(prevState => {
return /* e.target.valueも使って新たなstateを計算 */;
});
}
このように、setState
の関数内でイベントオブジェクトを使ってしまうと、関数が実行されるタイミングではe
はすでに無効となってしまっています。setState
に入る前に、イベントオブジェクトから必要な値を確保しておきましょう。
handleChange = (e) => {
const {value} = e.target;
this.setState(prevState => {
return /* valueも使って新たなstateを計算 */;
});
}
外側のクリックでドロップダウンを閉じる
例えばタイトルのような処理を組みたい場合に、Reactで管理しているわけではない外側にイベントを仕掛けざるを得ない場面もあります(componentWillUnmount
できちんと消す必要があります)。そして、外側にセットしたイベントの側で、.contains
を使ってReactコンポーネントの内側だったら無視…というように仕掛けたつもりだったのですが、「動的に削除したエレメントが内側だと認識されない」という、以前にRiotでハマったのと同様の問題を食らってしまいました。
display: none
も一案ではありますが、どうしようもない場合にはイベントを無効化したくなるかもしれません。ただ、そこも一筋縄では行きません。
Reactではイベントをシミュレーションして、すべてdocument
直下に仕掛けているので、document.body
などに仕掛けたイベントは、Reactのイベントが動く頃にはすべて実行済みです。componentDidMount
でdocument
に仕掛ければ、Reactより後にセットできますが、イベントキャンセル時にはe.nativeEvent.stopImmediatePropagation()
が必要となります(ネイティブ側でのキャンセルが必要なのと、Reactでは同じエレメントに複数イベントを仕掛けることがないので、ReactのイベントオブジェクトにはstopImmediatePropagation
がないことに要注意です)。