Skip to content

Commit 0639604

Browse files
blittlestaylor
authored andcommitted
fix: make sure HelmetData uses the same context data under the hood
RSC cannot serialize methods and functions between the server and client, so we need to support recreating `HelmetData` with the serialized data from the server. This works because `HelmetData` itself is just a wrapper over a context object and an instances array. Those can be the same refs in memory for multiple HelmetData instances.
1 parent 00b6deb commit 0639604

File tree

5 files changed

+40
-2
lines changed

5 files changed

+40
-2
lines changed

__tests__/server/__snapshots__/helmetData.test.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ exports[`Helmet Data server renders declarative without context 1`] = `"<base da
1111
exports[`Helmet Data server renders without context 1`] = `"<base data-rh=\\"true\\" target=\\"_blank\\" href=\\"http://localhost/\\"/>"`;
1212
1313
exports[`Helmet Data server sets base tag based on deepest nested component 1`] = `"<base data-rh=\\"true\\" href=\\"http://mysite.com/public\\"/>"`;
14+
15+
exports[`Helmet Data server works with the same context object but separate HelmetData instances 1`] = `"<base data-rh=\\"true\\" href=\\"http://mysite.com/public\\"/>"`;

__tests__/server/helmetData.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ describe('Helmet Data', () => {
7373
expect(head.base.toString).toBeDefined();
7474
expect(head.base.toString()).toMatchSnapshot();
7575
});
76+
77+
it('works with the same context object but separate HelmetData instances', () => {
78+
const context = {};
79+
const instances = [];
80+
81+
render(
82+
<div>
83+
<Helmet helmetData={new HelmetData(context, instances)}>
84+
<base href="http://mysite.com" />
85+
</Helmet>
86+
<Helmet helmetData={new HelmetData(context, instances)}>
87+
<base href="http://mysite.com/public" />
88+
</Helmet>
89+
</div>
90+
);
91+
92+
const head = context.helmet;
93+
94+
expect(head.base).toBeDefined();
95+
expect(head.base.toString).toBeDefined();
96+
expect(head.base.toString()).toMatchSnapshot();
97+
});
7698
});
7799

78100
describe('browser', () => {

index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ declare module 'react-helmet-async' {
8080
context?: {};
8181
}
8282

83+
export class HelmetData {
84+
constructor(context: any, instances?: Array<any>)
85+
}
86+
8387
export class HelmetProvider extends React.Component<ProviderProps> {
8488
static canUseDOM: boolean;
8589
}

src/HelmetData.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ export default class HelmetData {
1919
},
2020
};
2121

22-
constructor(context) {
22+
constructor(context, instances) {
2323
this.context = context;
2424

25+
if (instances) {
26+
this.instances = instances;
27+
}
28+
2529
if (!HelmetData.canUseDOM) {
2630
context.helmet = mapStateOnServer({
2731
baseTag: [],

src/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import fastCompare from 'react-fast-compare';
44
import invariant from 'invariant';
55
import { Context } from './Provider';
6+
import HelmetData from './HelmetData';
67
import Dispatcher from './Dispatcher';
78
import { TAG_NAMES, VALID_TAG_NAMES, HTML_TAG_MAP } from './constants';
89

@@ -220,13 +221,18 @@ export class Helmet extends Component {
220221
}
221222

222223
render() {
223-
const { children, helmetData, ...props } = this.props;
224+
const { children, ...props } = this.props;
224225
let newProps = { ...props };
226+
let { helmetData } = props;
225227

226228
if (children) {
227229
newProps = this.mapChildrenToProps(children, newProps);
228230
}
229231

232+
if (helmetData && !(helmetData instanceof HelmetData)) {
233+
helmetData = new HelmetData(helmetData.context, helmetData.instances);
234+
}
235+
230236
return helmetData ? (
231237
// eslint-disable-next-line react/jsx-props-no-spreading
232238
<Dispatcher {...newProps} context={helmetData.value} />

0 commit comments

Comments
 (0)