From 40923910b2018192a288df7e6437e5a5b988663f Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 21 Oct 2022 23:30:10 +0200 Subject: [PATCH] Fix useId not unique on shared component parent + DOM --- hooks/src/index.js | 42 ++++++++++++++++++-------------- hooks/src/internal.d.ts | 4 --- hooks/test/browser/useId.test.js | 40 ++++++++++++++++++++++++------ mangle.json | 3 +-- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/hooks/src/index.js b/hooks/src/index.js index 9f5d0981395..a4b6f790ef6 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -1,4 +1,4 @@ -import { options } from 'preact'; +import { Fragment, options } from 'preact'; /** @type {number} */ let currentIndex; @@ -27,22 +27,6 @@ const RAF_TIMEOUT = 100; let prevRaf; options._diff = vnode => { - if ( - typeof vnode.type === 'function' && - !vnode._mask && - // Ignore root Fragment node - vnode._parent !== null - ) { - vnode._mask = - (vnode._parent && vnode._parent._mask ? vnode._parent._mask : '') + - (vnode._parent && vnode._parent._children - ? vnode._parent._children.indexOf(vnode) - : 0); - } else if (!vnode._mask) { - vnode._mask = - vnode._parent && vnode._parent._mask ? vnode._parent._mask : ''; - } - currentComponent = null; if (oldBeforeDiff) oldBeforeDiff(vnode); }; @@ -395,7 +379,29 @@ function hash(s) { export function useId() { const state = getHookState(currentIndex++, 11); if (!state._value) { - state._value = 'P' + hash(currentComponent._vnode._mask) + currentIndex; + // Traverse the tree upwards and count the components until we reach + // the root node. + let parent = currentComponent._vnode._parent; + let root = currentComponent._vnode; + let i = 0; + while (parent !== null) { + if (parent.type !== Fragment && typeof parent.type === 'function') { + i++; + } + + root = parent; + parent = parent._parent; + } + + // Attach the id usage array to the root node and resize it to fit + let ids = root._ids || (root._ids = [0]); + while (ids.length < i) { + ids.push(0); + } + + // Increase the current component depth pointer + ids[i - 1]++; + state._value = 'P' + hash(ids.join('')) + currentIndex; } return state._value; diff --git a/hooks/src/internal.d.ts b/hooks/src/internal.d.ts index 58a2741384e..a555e4ec49a 100644 --- a/hooks/src/internal.d.ts +++ b/hooks/src/internal.d.ts @@ -38,10 +38,6 @@ export interface Component extends PreactComponent { __hooks?: ComponentHooks; } -export interface VNode extends PreactVNode { - _mask?: string; -} - export type HookState = | EffectHookState | MemoHookState diff --git a/hooks/test/browser/useId.test.js b/hooks/test/browser/useId.test.js index 232dffcf0a2..b7d25ebaeab 100644 --- a/hooks/test/browser/useId.test.js +++ b/hooks/test/browser/useId.test.js @@ -55,12 +55,12 @@ describe('useId', () => { render(, scratch); expect(scratch.innerHTML).to.equal( - '
h
' + '
h
' ); render(, scratch); expect(scratch.innerHTML).to.equal( - '
h
' + '
h
' ); }); @@ -83,12 +83,12 @@ describe('useId', () => { render(, scratch); expect(scratch.innerHTML).to.equal( - '
hhh
' + '
hhh
' ); render(, scratch); expect(scratch.innerHTML).to.equal( - '
hhh
' + '
hhh
' ); }); @@ -121,13 +121,13 @@ describe('useId', () => { render(, scratch); expect(scratch.innerHTML).to.equal( - '
h
' + '
h
' ); set(true); rerender(); expect(scratch.innerHTML).to.equal( - '
hh
' + '
hh
' ); }); @@ -350,6 +350,32 @@ describe('useId', () => { } render(, scratch); - expect(scratch.innerHTML).to.equal('

P481

P476951

'); + expect(scratch.innerHTML).to.equal('

P481

P15671

'); + }); + + it('should skip over HTML', () => { + const ids = []; + + function Foo() { + const id = useId(); + ids.push(id); + return

{id}

; + } + + function App() { + return ( +
+ + + + + + +
+ ); + } + + render(, scratch); + expect(ids).to.deep.equal(['P491', 'P501']); }); }); diff --git a/mangle.json b/mangle.json index 6633035754f..e51b759649d 100644 --- a/mangle.json +++ b/mangle.json @@ -39,7 +39,6 @@ "$_depth": "__b", "$_nextDom": "__d", "$_dirty": "__d", - "$_mask": "__m", "$_detachOnNextRender": "__b", "$_force": "__e", "$_nextState": "__s", @@ -81,4 +80,4 @@ "$_isSuspended": "__i" } } -} \ No newline at end of file +}