You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
useEscKeyDown: under React 18 StrictMode, a portal that mounts open leaves a stale Esc entry, and the portal below never gets top again #48
On React 18, under <React.StrictMode>, a portal that mounts already open leaves a permanent entry on the Esc stack. Once that portal closes, the portal that was open below it never gets top: true again, so Escape stops closing it. In antd: open a Drawer, open a Modal with destroyOnHidden from it, and close the Modal. From then on, Escape no longer closes the Drawer, however often it's pressed.
This is development only (StrictMode doesn't double-render in production), and React 19 isn't affected.
Versions
@rc-component/portal 2.2.1 (also current master, 4f29120)
constDemo=({ child }: {child: boolean})=>(<><PortalopenonEsc={onEscA}><div/></Portal>{child&&(<PortalopenonEsc={onEscB}><div/></Portal>)}</>);const{ rerender }=render(<Demochild={false}/>,{wrapper: React.StrictMode});rerender(<Demochild/>);// B mounts already openrerender(<Demochild={false}/>);// and closesfireEvent.keyDown(window,{key: 'Escape'});// React 18: onEscA is only ever called with { top: false }
The same happens in antd with:
a Modal with destroyOnHidden (or a Modal rendered only while open);
a nested Drawer;
a Tooltip shown and then hidden inside the Drawer.
A Modal without destroyOnHidden is fine, because its portal mounts closed and only later turns open.
Cause
useEscKeyDown pushes onto the stack during render, inside useMemo. On React 18, StrictMode renders a mounting component twice, and useId returns a different id each time (:r1: then :r2: in a browser). Effects only run for the second render. The first render's entry is never cleared: it sits above the portal that was already open, with a handler from a render React discarded.
The suite runs React 19, where this doesn't happen, so CI doesn't show it. On React 18.3.1, three Esc tests fail on master:
onEsc › onEsc should treat first mounted portal as top in StrictMode
onEsc › should clear stack when unmount
the regression test in the PR that fixes this
Proposed fix
Keep the render-time push, which keeps a parent below its children, but let an entry take part in Escape only once its effect has run. Once no live entry is left, drop the entries no effect ever claimed. Fix: #49.
Activity