Skip to content

Commit 0477f03

Browse files
committed
add new IDs for each each server renderer instance and prefixes to distinguish between each server render
1 parent dbd2626 commit 0477f03

10 files changed

+154
-20
lines changed

packages/react-art/src/ReactARTHostConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
488488
throw new Error('Not yet implemented');
489489
}
490490

491-
export function makeServerId(): OpaqueIDType {
491+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
492492
throw new Error('Not yet implemented');
493493
}
494494

packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js

+85
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,91 @@ describe('ReactDOMServerHooks', () => {
10041004
);
10051005
});
10061006

1007+
it('useOpaqueIdentifier prefix works for server renderer and does not clash', async () => {
1008+
function ChildTwo({id}) {
1009+
return <div id={id}>Child Three</div>;
1010+
}
1011+
function App() {
1012+
const id = useOpaqueIdentifier();
1013+
const idTwo = useOpaqueIdentifier();
1014+
1015+
return (
1016+
<div>
1017+
<div aria-labelledby={id}>Chid One</div>
1018+
<ChildTwo id={id} />
1019+
<div aria-labelledby={idTwo}>Child Three</div>
1020+
<div id={idTwo}>Child Four</div>
1021+
</div>
1022+
);
1023+
}
1024+
1025+
const containerOne = document.createElement('div');
1026+
document.body.append(containerOne);
1027+
1028+
containerOne.innerHTML = ReactDOMServer.renderToString(<App />, {
1029+
prefix: 'one',
1030+
});
1031+
1032+
const containerTwo = document.createElement('div');
1033+
document.body.append(containerTwo);
1034+
1035+
containerTwo.innerHTML = ReactDOMServer.renderToString(<App />, {
1036+
prefix: 'two',
1037+
});
1038+
1039+
expect(document.body.children.length).toEqual(2);
1040+
const childOne = document.body.children[0];
1041+
const childTwo = document.body.children[1];
1042+
1043+
expect(
1044+
childOne.children[0].children[0].getAttribute('aria-labelledby'),
1045+
).toEqual(childOne.children[0].children[1].getAttribute('id'));
1046+
expect(
1047+
childOne.children[0].children[2].getAttribute('aria-labelledby'),
1048+
).toEqual(childOne.children[0].children[3].getAttribute('id'));
1049+
1050+
expect(
1051+
childOne.children[0].children[0].getAttribute('aria-labelledby'),
1052+
).not.toEqual(
1053+
childOne.children[0].children[2].getAttribute('aria-labelledby'),
1054+
);
1055+
1056+
expect(
1057+
childOne.children[0].children[0]
1058+
.getAttribute('aria-labelledby')
1059+
.startsWith('one'),
1060+
).toBe(true);
1061+
expect(
1062+
childOne.children[0].children[2]
1063+
.getAttribute('aria-labelledby')
1064+
.includes('one'),
1065+
).toBe(true);
1066+
1067+
expect(
1068+
childTwo.children[0].children[0].getAttribute('aria-labelledby'),
1069+
).toEqual(childTwo.children[0].children[1].getAttribute('id'));
1070+
expect(
1071+
childTwo.children[0].children[2].getAttribute('aria-labelledby'),
1072+
).toEqual(childTwo.children[0].children[3].getAttribute('id'));
1073+
1074+
expect(
1075+
childTwo.children[0].children[0].getAttribute('aria-labelledby'),
1076+
).not.toEqual(
1077+
childTwo.children[0].children[2].getAttribute('aria-labelledby'),
1078+
);
1079+
1080+
expect(
1081+
childTwo.children[0].children[0]
1082+
.getAttribute('aria-labelledby')
1083+
.startsWith('two'),
1084+
).toBe(true);
1085+
expect(
1086+
childTwo.children[0].children[2]
1087+
.getAttribute('aria-labelledby')
1088+
.startsWith('two'),
1089+
).toBe(true);
1090+
});
1091+
10071092
it('useOpaqueIdentifier: IDs match when, after hydration, a new component that uses the ID is rendered', async () => {
10081093
let _setShowDiv;
10091094
function App() {

packages/react-dom/src/client/ReactDOMHostConfig.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1147,9 +1147,8 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
11471147
};
11481148
}
11491149

1150-
let serverId: number = 0;
1151-
export function makeServerId(): OpaqueIDType {
1152-
return 'R:' + (serverId++).toString(36);
1150+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
1151+
return (prefix || '') + 'R:' + serverId.toString(36);
11531152
}
11541153

11551154
export function isOpaqueHydratingObject(value: mixed): boolean {

packages/react-dom/src/server/ReactDOMNodeStreamRenderer.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
7+
import type {PartialRendererOptions} from './ReactPartialRenderer';
78

89
import {Readable} from 'stream';
910

@@ -36,15 +37,21 @@ class ReactMarkupReadableStream extends Readable {
3637
* server.
3738
* See https://reactjs.org/docs/react-dom-server.html#rendertonodestream
3839
*/
39-
export function renderToNodeStream(element) {
40-
return new ReactMarkupReadableStream(element, false);
40+
export function renderToNodeStream(
41+
element,
42+
options: PartialRendererOptions | void,
43+
) {
44+
return new ReactMarkupReadableStream(element, false, options);
4145
}
4246

4347
/**
4448
* Similar to renderToNodeStream, except this doesn't create extra DOM attributes
4549
* such as data-react-id that React uses internally.
4650
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticnodestream
4751
*/
48-
export function renderToStaticNodeStream(element) {
49-
return new ReactMarkupReadableStream(element, true);
52+
export function renderToStaticNodeStream(
53+
element,
54+
options: PartialRendererOptions | void,
55+
) {
56+
return new ReactMarkupReadableStream(element, true, options);
5057
}

packages/react-dom/src/server/ReactDOMStringRenderer.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import type {PartialRendererOptions} from './ReactPartialRenderer';
89
import ReactPartialRenderer from './ReactPartialRenderer';
910

1011
/**
1112
* Render a ReactElement to its initial HTML. This should only be used on the
1213
* server.
1314
* See https://reactjs.org/docs/react-dom-server.html#rendertostring
1415
*/
15-
export function renderToString(element) {
16-
const renderer = new ReactPartialRenderer(element, false);
16+
export function renderToString(
17+
element,
18+
options?: void | PartialRendererOptions,
19+
) {
20+
const renderer = new ReactPartialRenderer(element, false, options);
1721
try {
1822
const markup = renderer.read(Infinity);
1923
return markup;
@@ -27,10 +31,13 @@ export function renderToString(element) {
2731
* such as data-react-id that React uses internally.
2832
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
2933
*/
30-
export function renderToStaticMarkup(element) {
31-
const renderer = new ReactPartialRenderer(element, true);
34+
export function renderToStaticMarkup(
35+
element,
36+
options?: void | PartialRendererOptions,
37+
) {
38+
const renderer = new ReactPartialRenderer(element, true, options);
3239
try {
33-
const markup = renderer.read(Infinity);
40+
const markup = renderer.read(Infinity, options);
3441
return markup;
3542
} finally {
3643
renderer.destroy();

packages/react-dom/src/server/ReactPartialRenderer.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ import {
6161
Dispatcher,
6262
currentThreadID,
6363
setCurrentThreadID,
64+
currentUniqueID,
65+
setCurrentUniqueID,
66+
uniqueIDPrefix,
67+
setCurrentUniqueIDPrefix,
6468
} from './ReactPartialRendererHooks';
6569
import {
6670
Namespaces,
@@ -78,6 +82,10 @@ import {validateProperties as validateARIAProperties} from '../shared/ReactDOMIn
7882
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
7983
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
8084

85+
export type PartialRendererOptions = {
86+
prefix?: string,
87+
};
88+
8189
// Based on reading the React.Children implementation. TODO: type this somewhere?
8290
type ReactNode = string | number | ReactElement;
8391
type FlatReactChildren = Array<null | ReactNode>;
@@ -725,7 +733,14 @@ class ReactDOMServerRenderer {
725733
contextValueStack: Array<any>;
726734
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
727735

728-
constructor(children: mixed, makeStaticMarkup: boolean) {
736+
uniqueID: number;
737+
prefix: string;
738+
739+
constructor(
740+
children: mixed,
741+
makeStaticMarkup: boolean,
742+
options?: PartialRendererOptions | void,
743+
) {
729744
const flatChildren = flattenTopLevelChildren(children);
730745

731746
const topFrame: Frame = {
@@ -753,6 +768,11 @@ class ReactDOMServerRenderer {
753768
this.contextIndex = -1;
754769
this.contextStack = [];
755770
this.contextValueStack = [];
771+
772+
// useOpaqueIdentifier ID
773+
this.uniqueID = 0;
774+
this.prefix = (options && options.prefix) || '';
775+
756776
if (__DEV__) {
757777
this.contextProviderStack = [];
758778
}
@@ -838,6 +858,10 @@ class ReactDOMServerRenderer {
838858

839859
const prevThreadID = currentThreadID;
840860
setCurrentThreadID(this.threadID);
861+
const prevUniqueID = currentUniqueID;
862+
setCurrentUniqueID(this.uniqueID);
863+
const prevUniquePrefix = uniqueIDPrefix;
864+
setCurrentUniqueIDPrefix(this.prefix);
841865
const prevDispatcher = ReactCurrentDispatcher.current;
842866
ReactCurrentDispatcher.current = Dispatcher;
843867
try {
@@ -935,6 +959,8 @@ class ReactDOMServerRenderer {
935959
} finally {
936960
ReactCurrentDispatcher.current = prevDispatcher;
937961
setCurrentThreadID(prevThreadID);
962+
setCurrentUniqueID(prevUniqueID);
963+
setCurrentUniqueIDPrefix(prevUniquePrefix);
938964
}
939965
}
940966

packages/react-dom/src/server/ReactPartialRendererHooks.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,19 @@ function useTransition(
495495
return [startTransition, false];
496496
}
497497

498+
export let currentUniqueID: number = 0;
499+
export let uniqueIDPrefix: string = '';
500+
501+
export function setCurrentUniqueIDPrefix(prefix: string) {
502+
uniqueIDPrefix = prefix;
503+
}
504+
505+
export function setCurrentUniqueID(id: number) {
506+
currentUniqueID = id;
507+
}
508+
498509
function useOpaqueIdentifier(): OpaqueIDType {
499-
return makeServerId();
510+
return makeServerId(uniqueIDPrefix, currentUniqueID++);
500511
}
501512

502513
function useEvent(event: any): ReactDOMListenerMap {

packages/react-native-renderer/src/ReactFabricHostConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
505505
throw new Error('Not yet implemented');
506506
}
507507

508-
export function makeServerId(): OpaqueIDType {
508+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
509509
throw new Error('Not yet implemented');
510510
}
511511

packages/react-native-renderer/src/ReactNativeHostConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
554554
throw new Error('Not yet implemented');
555555
}
556556

557-
export function makeServerId(): OpaqueIDType {
557+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
558558
throw new Error('Not yet implemented');
559559
}
560560

packages/react-test-renderer/src/ReactTestHostConfig.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,8 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
407407
};
408408
}
409409

410-
let serverId: number = 0;
411-
export function makeServerId(): OpaqueIDType {
412-
return 's_' + (serverId++).toString(36);
410+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
411+
return (prefix || '') + 's_' + serverId.toString(36);
413412
}
414413

415414
export function isOpaqueHydratingObject(value: mixed): boolean {

0 commit comments

Comments
 (0)