Skip to content

Commit

Permalink
Fix useId mismatch due to top level Fragments
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed Oct 6, 2022
1 parent 90d92e6 commit aa12b3c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-elephants-occur.md
@@ -0,0 +1,5 @@
---
'preact-render-to-string': patch
---

Fix vnode masks not matching with core due to top level component Fragments
38 changes: 14 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -115,7 +115,7 @@
"lint-staged": "^10.5.3",
"microbundle": "^0.13.0",
"mocha": "^8.2.1",
"preact": "^10.5.7",
"preact": "^10.11.1",
"prettier": "^2.2.1",
"sinon": "^9.2.2",
"sinon-chai": "^3.5.0",
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Expand Up @@ -239,6 +239,12 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
}
}

// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment =
rendered != null && rendered.type === Fragment && rendered.key == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;

// Recurse into children before invoking the after-diff hook
const str = _renderToString(
rendered,
Expand Down
55 changes: 54 additions & 1 deletion test/render.test.js
Expand Up @@ -5,7 +5,8 @@ import {
useContext,
useEffect,
useLayoutEffect,
useMemo
useMemo,
useId
} from 'preact/hooks';
import { expect } from 'chai';
import { spy, stub, match } from 'sinon';
Expand Down Expand Up @@ -1268,4 +1269,56 @@ describe('render', () => {
it('should not render function children', () => {
expect(render(<div>{() => {}}</div>)).to.equal('<div></div>');
});

describe('vnode masks (useId)', () => {
it('should work with Fragments', () => {
const ids = [];
function Foo() {
const id = useId();
ids.push(id);
return <p>{id}</p>;
}

function Bar(props) {
return props.children;
}

function App() {
return (
<Bar>
<Foo />
<Fragment>
<Foo />
</Fragment>
</Bar>
);
}

render(<App />);
expect(ids[0]).not.to.equal(ids[1]);
});

it('should skip component top level Fragment child', () => {
const Wrapper = ({ children }) => <Fragment>{children}</Fragment>;

function Foo() {
const id = useId();
return <p>{id}</p>;
}

function App() {
const id = useId();
return (
<div>
<p>{id}</p>
<Wrapper>
<Foo />
</Wrapper>
</div>
);
}

expect(render(<App />)).to.equal('<div><p>P481</p><p>P476951</p></div>');
});
});
});

0 comments on commit aa12b3c

Please sign in to comment.