React bug: disabledにしたボタンがonMouseEnterやonMouseLeaveイベントを妨げる


disabledにしたボタンの上からすぐ下の要素にマウスポインタを移してもonMouseEnterイベントが起こらない

disabledにしたボタンのすぐ下に、onMouseEnterイベントハンドラを定めた要素が置かれたときに生じる問題です。disabledにしたボタンの上から下の要素の領域にマウスポインタを入れても、onMouseEnterイベントが起こりません。Chromeで確認されました。

GitHubのReactを見ると、まさにそのものズバリのissue(問題)「Bug: MouseEnter does not fire when coming from disabled elements」(2020/07/21)がいまだopen(未解決)のままになっています。マウスイベントの扱いを最適化する際に取りこぼしがあったようで、直すにはあちこちに手を入れなければならないらしいです。

確認のためのコードと対処法

つぎに掲げるのが、問題を確かめるための簡単なコードです。button要素はdisabledとし、そのすぐ下の要素(div)にonMouseEnteronMouseLeaveのイベントハンドラを定めます。ボタンの上をとおって、下の領域にマウスポインタが入ると、onMouseEnterイベントは起こりません(Chromeの場合)。

function App() {
    const mouseEnterHandler = (event) => {
        console.log('mouse enter');
    };
    const mouseLeaveHandler = (event) => {
        console.log('mouse leave');
    };
    return (
        <div>
            <button disabled>disabledボタン<br />
            この上をとおって下の領域に入る</button>
            <div
                onMouseEnter={mouseEnterHandler}
                onMouseLeave={mouseLeaveHandler}
            >
                onMouseEnter/onMouseLeaveハンドラからコンソール出力
            </div>
        </div>
    );
}

disabledの属性を与えたボタンは基本、マウスイベントを受け取らなくてよいでしょう。だとすると、つぎのように親要素(span)に包んで、マウスイベントをスルーさせれば(pointer-eventsプロパティ値none)、下の要素はonMouseEnterが受け取れます。

<span style={{ pointerEvents: 'none' }}>
    <button disabled>disabledボタン<br />
    親で包んでマウスイベントをスルー</button>
</span>

動きが確かめられるように、テスト用のコードをCodePenに掲げました。左が問題のdisabledボタンです。右のボタンは親に包んでマウスイベントをスルーさせています。

See the Pen React bug: onMouseEnter does not fire when coming from disabled button elements by Fumio Nonaka (@FumioNonaka) on CodePen.

disabledのボタンを含めると親のonMouseLeaveイベントが起こらない

React does not fire onMouseLeave events on a disabled button」は、onMouseLeaveイベントが起こらない例を紹介しています。イベントハンドラが定められた要素に、disabledのボタンを含んだ場合です。CodeSandboxにコード例が公開されているので、試してみることもできます。こちらは、つねに呼び出されないというわけではないようです。おそらく、前述の問題と根は同じでしょう。

もっとも、disabledボタン自身にonMouseLeaveハンドラを書いたとき実行されないというのは、むしろ正しい動きに思えます。onMouseEnterハンドラが呼び出される方が問題です。標準JavaScriptコードでイベントリスナーを定めたときは、どちらのイベントも起こりません。