こんな感じの仕様のドロップダウンを実装するとします。
- クリックだけでなく、 Tab でフォーカスしたときにも開く
- クリックの場合はイベントをトラッキングしてからドロップダウンを開く
色々足りませんがこんな感じのコードを書いたとします:
class Dropdown extends React.Component {
open() {
this.setState({ isOpen: true });
}
render() {
return (
<div
tabIndex="0"
onClick={e => {
trackClickEvent(e).then(() => this.open());
}}
onFocus={this.open.bind(this)}
>
{/* ... */}
</div>
)
}
}
しかし、このコードは意図したように動きません。クリック時に onFocus
だけが実行されて onClick
が実行されないのです。何故でしょうか?
(おそらく)onClick
が実行されない理由
まずブラウザの仕様として(要出典) フォーカス可能な要素をクリックすると、先に FocusEvent
が発生し、その後でクリックによる MouseEvent
が発生します。つまり onFocus
が先に実行されます。
そして(React の中を追っていないので正確なところは分からないのですが) onFocus
の中で setState
すると次のフレームで再レンダリングされ、その過程で何かがあって onClick
が実行されなくなります。
対処方法
onFocus
の前に onMouseDown
が実行されるので、 onClick
の代わりにそちらを使うという方法があります:
<div
tabIndex="0"
- onClick={e => {
+ onMouseDown={e => {
trackClickEvent(e).then(() => this.open());
}}
onFocus={this.open.bind(this)}
>
この場合たしかに onMouseDown
が先に実行されるのですが、なぜか onFocus
が実行されます
おわりに
ちなみに同じく VDom を使っている hyperapp でも同じような現象が発生するので、 React の問題というよりはブラウザの挙動なのではないかと思います。