Skip to content

Commit a840c2a

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

10 files changed

+151
-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.internal.js

+85
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,91 @@ describe('ReactDOMServerHooks', () => {
10081008
);
10091009
});
10101010

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

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -1174,9 +1174,8 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
11741174
};
11751175
}
11761176

1177-
let serverId: number = 0;
1178-
export function makeServerId(): OpaqueIDType {
1179-
return 'R:' + (serverId++).toString(36);
1177+
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
1178+
return (prefix || '') + 'R:' + serverId.toString(36);
11801179
}
11811180

11821181
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

+24-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+
currentUniqueIDPrefix,
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,11 @@ class ReactDOMServerRenderer {
725733
contextValueStack: Array<any>;
726734
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
727735

728-
constructor(children: mixed, makeStaticMarkup: boolean) {
736+
constructor(
737+
children: mixed,
738+
makeStaticMarkup: boolean,
739+
options?: PartialRendererOptions | void,
740+
) {
729741
const flatChildren = flattenTopLevelChildren(children);
730742

731743
const topFrame: Frame = {
@@ -753,6 +765,11 @@ class ReactDOMServerRenderer {
753765
this.contextIndex = -1;
754766
this.contextStack = [];
755767
this.contextValueStack = [];
768+
769+
// useOpaqueIdentifier ID
770+
this.uniqueID = 0;
771+
this.prefix = (options && options.prefix) || '';
772+
756773
if (__DEV__) {
757774
this.contextProviderStack = [];
758775
}
@@ -838,6 +855,10 @@ class ReactDOMServerRenderer {
838855

839856
const prevThreadID = currentThreadID;
840857
setCurrentThreadID(this.threadID);
858+
const prevUniqueID = currentUniqueID;
859+
setCurrentUniqueID(this.uniqueID);
860+
const prevUniquePrefix = currentUniqueIDPrefix;
861+
setCurrentUniqueIDPrefix(this.prefix);
841862
const prevDispatcher = ReactCurrentDispatcher.current;
842863
ReactCurrentDispatcher.current = Dispatcher;
843864
try {
@@ -935,6 +956,8 @@ class ReactDOMServerRenderer {
935956
} finally {
936957
ReactCurrentDispatcher.current = prevDispatcher;
937958
setCurrentThreadID(prevThreadID);
959+
setCurrentUniqueID(prevUniqueID);
960+
setCurrentUniqueIDPrefix(prevUniquePrefix);
938961
}
939962
}
940963

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)