π Bug report
When rendering a Dialog with open={true} on mount in React Strict Mode, the dialog content element gets incorrect data-has-nested="dialog" and data-nested attributes, as if a nested dialog were open β even though only a single dialog exists in the DOM.
π₯ Steps to reproduce
- Render a
<Dialog.Root open> that mounts with open={true} (controlled, not triggered by user interaction)
- Run in React development mode (Strict Mode enabled)
- Inspect the
[data-scope="dialog"][data-part="content"] element
Observed:
data-has-nested="dialog" is set on the only dialog in the DOM
--layer-index: 1 instead of 0
--nested-layer-count: 1 instead of 0
In production mode (Strict Mode disabled), the bug does not occur.
π» Link to reproduction
https://codesandbox.io/p/devbox/github/Alexandre-GL/zag-layerstack-repro/main
GitHub repo: https://github.com/Alexandre-GL/zag-layerstack-repro
git clone <repo>
npm install
npm run dev
# Open http://localhost:5173, click "Mount always-open dialog"
π§ Expected behavior
A single open dialog should have:
--layer-index: 0
--nested-layer-count: 0
- No
data-has-nested attribute
π§ Possible Solution
It seems like layerStack ends up with two entries for the same DOM node. This is likely caused by React Strict Mode's double-invocation of effects, where the cleanup doesn't fully undo the first layerStack.add() call before the second one runs.
A possible fix would be to add a deduplication guard in layerStack.add():
// packages/utilities/dismissable/src/layer-stack.ts
add(layer: Layer) {
const existingIndex = this.indexOf(layer.node);
if (existingIndex !== -1) {
this.layers.splice(existingIndex, 1);
}
this.layers.push(layer);
this.syncLayers();
},
This way, if the same DOM node is added again, the previous entry is removed first β making add() idempotent.
We are currently working around this with a pnpm patch on @zag-js/dismissable using the deduplication guard above. Happy to open a PR if that approach looks right.
π System information
| Software |
Version |
| Zag Version |
1.39.1 |
| Browser |
Chrome 137 |
| Operating System |
Linux |
Tested via @ark-ui/[email protected] which depends on @zag-js/*@1.39.1.
π Bug report
When rendering a Dialog with
open={true}on mount in React Strict Mode, the dialog content element gets incorrectdata-has-nested="dialog"anddata-nestedattributes, as if a nested dialog were open β even though only a single dialog exists in the DOM.π₯ Steps to reproduce
<Dialog.Root open>that mounts withopen={true}(controlled, not triggered by user interaction)[data-scope="dialog"][data-part="content"]elementObserved:
data-has-nested="dialog"is set on the only dialog in the DOM--layer-index: 1instead of0--nested-layer-count: 1instead of0In production mode (Strict Mode disabled), the bug does not occur.
π» Link to reproduction
https://codesandbox.io/p/devbox/github/Alexandre-GL/zag-layerstack-repro/main
GitHub repo: https://github.com/Alexandre-GL/zag-layerstack-repro
π§ Expected behavior
A single open dialog should have:
--layer-index: 0--nested-layer-count: 0data-has-nestedattributeπ§ Possible Solution
It seems like
layerStackends up with two entries for the same DOM node. This is likely caused by React Strict Mode's double-invocation of effects, where the cleanup doesn't fully undo the firstlayerStack.add()call before the second one runs.A possible fix would be to add a deduplication guard in
layerStack.add():This way, if the same DOM node is added again, the previous entry is removed first β making
add()idempotent.We are currently working around this with a
pnpm patchon@zag-js/dismissableusing the deduplication guard above. Happy to open a PR if that approach looks right.π System information
Tested via
@ark-ui/[email protected]which depends on@zag-js/*@1.39.1.