diff --git a/compat/src/portals.js b/compat/src/portals.js index c480a3d3a3..b32353bbfd 100644 --- a/compat/src/portals.js +++ b/compat/src/portals.js @@ -32,6 +32,12 @@ function Portal(props) { } if (!_this._temp) { + // Ensure the element has a mask for useId invocations + let root = _this._vnode; + while (root !== null && !root._mask && root._parent !== null) { + root = root._parent; + } + _this._container = container; // Create a fake DOM parent node that manages a subset of `container`'s children: @@ -39,6 +45,7 @@ function Portal(props) { nodeType: 1, parentNode: container, childNodes: [], + _children: { _mask: root._mask }, contains: () => true, // Technically this isn't needed appendChild(child) { diff --git a/compat/test/browser/portals.test.js b/compat/test/browser/portals.test.js index d005498f87..51a0111b0f 100644 --- a/compat/test/browser/portals.test.js +++ b/compat/test/browser/portals.test.js @@ -5,10 +5,12 @@ import React, { useState, Component, useEffect, - Fragment + Fragment, + useId } from 'preact/compat'; import { setupScratch, teardown } from '../../../test/_util/helpers'; import { setupRerender, act } from 'preact/test-utils'; +import { expect } from 'chai'; /* eslint-disable react/jsx-boolean-value, react/display-name, prefer-arrow-callback */ @@ -212,6 +214,38 @@ describe('Portal', () => { expect(scratch.firstChild.firstChild.childNodes.length).to.equal(0); }); + it('should have unique ids for each portal', () => { + let root = document.createElement('div'); + let dialog = document.createElement('div'); + dialog.id = 'container'; + + scratch.appendChild(root); + scratch.appendChild(dialog); + + function Id() { + const id = useId(); + return id; + } + + function Dialog() { + return ; + } + + function App() { + return ( +
+ + {createPortal(, dialog)} +
+ ); + } + + render(, root); + expect(scratch.innerHTML).to.equal( + '
P0-0
P0-1
' + ); + }); + it('should unmount Portal', () => { let root = document.createElement('div'); let dialog = document.createElement('div'); @@ -237,8 +271,8 @@ describe('Portal', () => { it('should leave a working root after the portal', () => { /** @type {() => void} */ let toggle, - /** @type {() => void} */ - toggle2; + /** @type {() => void} */ + toggle2; function Foo(props) { const [mounted, setMounted] = useState(false); @@ -289,8 +323,8 @@ describe('Portal', () => { it('should work with stacking portals', () => { /** @type {() => void} */ let toggle, - /** @type {() => void} */ - toggle2; + /** @type {() => void} */ + toggle2; function Foo(props) { const [mounted, setMounted] = useState(false); @@ -377,8 +411,8 @@ describe('Portal', () => { it('should work with replacing placeholder portals', () => { /** @type {() => void} */ let toggle, - /** @type {() => void} */ - toggle2; + /** @type {() => void} */ + toggle2; function Foo(props) { const [mounted, setMounted] = useState(false); @@ -463,8 +497,9 @@ describe('Portal', () => { it('should support nested portals', () => { /** @type {() => void} */ let toggle, - /** @type {() => void} */ - toggle2, inner; + /** @type {() => void} */ + toggle2, + inner; function Bar() { const [mounted, setMounted] = useState(false);