diff --git a/hooks/src/index.js b/hooks/src/index.js index 9f5d098139..d05947eaaf 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -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); }; @@ -383,19 +367,18 @@ export function useErrorBoundary(cb) { ]; } -function hash(s) { - let h = 0, - i = s.length; - while (i > 0) { - h = ((h << 5) - h + s.charCodeAt(--i)) | 0; - } - return h; -} - export function useId() { const state = getHookState(currentIndex++, 11); if (!state._value) { - state._value = 'P' + hash(currentComponent._vnode._mask) + currentIndex; + // Grab either the root node or the nearest async boundary node. + /** @type {import('./internal.d').VNode} */ + let root = currentComponent._vnode; + while (root !== null && !root._mask && root._parent !== null) { + root = root._parent; + } + + let mask = root._mask || (root._mask = [0, 0]); + state._value = 'P' + mask[0] + '-' + mask[1]++; } return state._value; diff --git a/hooks/src/internal.d.ts b/hooks/src/internal.d.ts index 58a2741384..4d4be511f7 100644 --- a/hooks/src/internal.d.ts +++ b/hooks/src/internal.d.ts @@ -39,7 +39,7 @@ export interface Component extends PreactComponent { } export interface VNode extends PreactVNode { - _mask?: string; + _mask?: [number, number]; } export type HookState = diff --git a/hooks/test/browser/useId.test.js b/hooks/test/browser/useId.test.js index 232dffcf0a..e7fc7f947b 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
' ); }); @@ -332,13 +332,16 @@ describe('useId', () => { return {children}; }; + const ids = []; function Foo() { const id = useId(); + ids.push(id); return

{id}

; } function App() { const id = useId(); + ids.push(id); return (

{id}

@@ -350,6 +353,84 @@ describe('useId', () => { } render(, scratch); - expect(scratch.innerHTML).to.equal('

P481

P476951

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

{id}

; + } + + function App() { + return ( +
+ + + + + + +
+ ); + } + + render(, scratch); + expect(ids[0]).not.to.equal(ids[1]); + }); + + it('should reset for each renderToString roots', () => { + const ids = []; + + function Foo() { + const id = useId(); + ids.push(id); + return

{id}

; + } + + function App() { + return ( +
+ + + + + + +
+ ); + } + + const res1 = rts(); + const res2 = rts(); + expect(res1).to.equal(res2); + }); + + it('should work with conditional components', () => { + function Foo() { + const id = useId(); + return

{id}

; + } + function Bar() { + const id = useId(); + return

{id}

; + } + + let update; + function App() { + const [v, setV] = useState(false); + update = setV; + return
{!v ? : }
; + } + + render(, scratch); + const first = scratch.innerHTML; + + update(v => !v); + rerender(); + expect(first).not.to.equal(scratch.innerHTML); }); }); diff --git a/mangle.json b/mangle.json index 6633035754..22b96e72bf 100644 --- a/mangle.json +++ b/mangle.json @@ -81,4 +81,4 @@ "$_isSuspended": "__i" } } -} \ No newline at end of file +}