Skip to content

Commit 1134a11

Browse files
committed
add new IDs for each each server renderer instance and prefixes to distinguish between each server render
1 parent 53ce0c3 commit 1134a11

12 files changed

+208
-51
lines changed

packages/react-art/src/ReactARTHostConfig.js

-4
Original file line numberDiff line numberDiff line change
@@ -489,10 +489,6 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
489489
throw new Error('Not yet implemented');
490490
}
491491

492-
export function makeServerId(): OpaqueIDType {
493-
throw new Error('Not yet implemented');
494-
}
495-
496492
export function beforeActiveInstanceBlur() {
497493
// noop
498494
}

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

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

1007+
it('useOpaqueIdentifier identifierPrefix 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+
identifierPrefix: 'one',
1030+
});
1031+
1032+
const containerTwo = document.createElement('div');
1033+
document.body.append(containerTwo);
1034+
1035+
containerTwo.innerHTML = ReactDOMServer.renderToString(<App />, {
1036+
identifierPrefix: '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+
1092+
it('useOpaqueIdentifier identifierPrefix works for multiple reads on a streaming server renderer', async () => {
1093+
function ChildTwo() {
1094+
const id = useOpaqueIdentifier();
1095+
1096+
return <div id={id}>Child Two</div>;
1097+
}
1098+
1099+
function App() {
1100+
const id = useOpaqueIdentifier();
1101+
1102+
return (
1103+
<>
1104+
<div id={id}>Child One</div>
1105+
<ChildTwo />
1106+
<div aria-labelledby={id}>Aria One</div>
1107+
</>
1108+
);
1109+
}
1110+
1111+
const container = document.createElement('div');
1112+
document.body.append(container);
1113+
1114+
const streamOne = ReactDOMServer.renderToNodeStream(<App />, {
1115+
identifierPrefix: 'one',
1116+
}).setEncoding('utf8');
1117+
const streamTwo = ReactDOMServer.renderToNodeStream(<App />, {
1118+
identifierPrefix: 'two',
1119+
}).setEncoding('utf8');
1120+
1121+
const containerOne = document.createElement('div');
1122+
const containerTwo = document.createElement('div');
1123+
1124+
streamOne._read(10);
1125+
streamTwo._read(10);
1126+
1127+
containerOne.innerHTML = streamOne.read();
1128+
containerTwo.innerHTML = streamTwo.read();
1129+
1130+
expect(containerOne.children[0].getAttribute('id')).not.toEqual(
1131+
containerOne.children[1].getAttribute('id'),
1132+
);
1133+
expect(containerTwo.children[0].getAttribute('id')).not.toEqual(
1134+
containerTwo.children[1].getAttribute('id'),
1135+
);
1136+
expect(containerOne.children[0].getAttribute('id')).not.toEqual(
1137+
containerTwo.children[0].getAttribute('id'),
1138+
);
1139+
expect(
1140+
containerOne.children[0].getAttribute('id').includes('one'),
1141+
).toBe(true);
1142+
expect(
1143+
containerOne.children[1].getAttribute('id').includes('one'),
1144+
).toBe(true);
1145+
expect(
1146+
containerTwo.children[0].getAttribute('id').includes('two'),
1147+
).toBe(true);
1148+
expect(
1149+
containerTwo.children[1].getAttribute('id').includes('two'),
1150+
).toBe(true);
1151+
1152+
expect(containerOne.children[1].getAttribute('id')).not.toEqual(
1153+
containerTwo.children[1].getAttribute('id'),
1154+
);
1155+
expect(containerOne.children[0].getAttribute('id')).toEqual(
1156+
containerOne.children[2].getAttribute('aria-labelledby'),
1157+
);
1158+
expect(containerTwo.children[0].getAttribute('id')).toEqual(
1159+
containerTwo.children[2].getAttribute('aria-labelledby'),
1160+
);
1161+
});
1162+
10071163
it('useOpaqueIdentifier: IDs match when, after hydration, a new component that uses the ID is rendered', async () => {
10081164
let _setShowDiv;
10091165
function App() {

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

-5
Original file line numberDiff line numberDiff line change
@@ -1078,11 +1078,6 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
10781078
};
10791079
}
10801080

1081-
let serverId: number = 0;
1082-
export function makeServerId(): OpaqueIDType {
1083-
return 'R:' + (serverId++).toString(36);
1084-
}
1085-
10861081
export function isOpaqueHydratingObject(value: mixed): boolean {
10871082
return (
10881083
value !== null &&

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

+11-6
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@
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 {ServerOptions} from './ReactPartialRenderer';
78

89
import {Readable} from 'stream';
910

1011
import ReactPartialRenderer from './ReactPartialRenderer';
1112

1213
// This is a Readable Node.js stream which wraps the ReactDOMPartialRenderer.
1314
class ReactMarkupReadableStream extends Readable {
14-
constructor(element, makeStaticMarkup) {
15+
constructor(element, makeStaticMarkup, options) {
1516
// Calls the stream.Readable(options) constructor. Consider exposing built-in
1617
// features like highWaterMark in the future.
1718
super({});
18-
this.partialRenderer = new ReactPartialRenderer(element, makeStaticMarkup);
19+
this.partialRenderer = new ReactPartialRenderer(
20+
element,
21+
makeStaticMarkup,
22+
options,
23+
);
1924
}
2025

2126
_destroy(err, callback) {
@@ -36,15 +41,15 @@ class ReactMarkupReadableStream extends Readable {
3641
* server.
3742
* See https://reactjs.org/docs/react-dom-server.html#rendertonodestream
3843
*/
39-
export function renderToNodeStream(element) {
40-
return new ReactMarkupReadableStream(element, false);
44+
export function renderToNodeStream(element, options?: ServerOptions) {
45+
return new ReactMarkupReadableStream(element, false, options);
4146
}
4247

4348
/**
4449
* Similar to renderToNodeStream, except this doesn't create extra DOM attributes
4550
* such as data-react-id that React uses internally.
4651
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticnodestream
4752
*/
48-
export function renderToStaticNodeStream(element) {
49-
return new ReactMarkupReadableStream(element, true);
53+
export function renderToStaticNodeStream(element, options?: ServerOptions) {
54+
return new ReactMarkupReadableStream(element, true, options);
5055
}

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

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

8+
import type {ServerOptions} 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(element, options?: ServerOptions) {
17+
const renderer = new ReactPartialRenderer(element, false, options);
1718
try {
1819
const markup = renderer.read(Infinity);
1920
return markup;
@@ -27,8 +28,8 @@ export function renderToString(element) {
2728
* such as data-react-id that React uses internally.
2829
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
2930
*/
30-
export function renderToStaticMarkup(element) {
31-
const renderer = new ReactPartialRenderer(element, true);
31+
export function renderToStaticMarkup(element, options?: ServerOptions) {
32+
const renderer = new ReactPartialRenderer(element, true, options);
3233
try {
3334
const markup = renderer.read(Infinity);
3435
return markup;

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

+22-6
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ import {
6060
prepareToUseHooks,
6161
finishHooks,
6262
Dispatcher,
63-
currentThreadID,
64-
setCurrentThreadID,
63+
currentPartialRenderer,
64+
setCurrentPartialRenderer,
6565
} from './ReactPartialRendererHooks';
6666
import {
6767
Namespaces,
@@ -79,6 +79,10 @@ import {validateProperties as validateARIAProperties} from '../shared/ReactDOMIn
7979
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
8080
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
8181

82+
export type ServerOptions = {
83+
identifierPrefix?: string,
84+
};
85+
8286
// Based on reading the React.Children implementation. TODO: type this somewhere?
8387
type ReactNode = string | number | ReactElement;
8488
type FlatReactChildren = Array<null | ReactNode>;
@@ -726,7 +730,14 @@ class ReactDOMServerRenderer {
726730
contextValueStack: Array<any>;
727731
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
728732

729-
constructor(children: mixed, makeStaticMarkup: boolean) {
733+
uniqueID: number;
734+
identifierPrefix: string;
735+
736+
constructor(
737+
children: mixed,
738+
makeStaticMarkup: boolean,
739+
options?: ServerOptions,
740+
) {
730741
const flatChildren = flattenTopLevelChildren(children);
731742

732743
const topFrame: Frame = {
@@ -754,6 +765,11 @@ class ReactDOMServerRenderer {
754765
this.contextIndex = -1;
755766
this.contextStack = [];
756767
this.contextValueStack = [];
768+
769+
// useOpaqueIdentifier ID
770+
this.uniqueID = 0;
771+
this.identifierPrefix = (options && options.identifierPrefix) || '';
772+
757773
if (__DEV__) {
758774
this.contextProviderStack = [];
759775
}
@@ -837,8 +853,8 @@ class ReactDOMServerRenderer {
837853
return null;
838854
}
839855

840-
const prevThreadID = currentThreadID;
841-
setCurrentThreadID(this.threadID);
856+
const prevPartialRenderer = currentPartialRenderer;
857+
setCurrentPartialRenderer(this);
842858
const prevDispatcher = ReactCurrentDispatcher.current;
843859
ReactCurrentDispatcher.current = Dispatcher;
844860
try {
@@ -935,7 +951,7 @@ class ReactDOMServerRenderer {
935951
return out[0];
936952
} finally {
937953
ReactCurrentDispatcher.current = prevDispatcher;
938-
setCurrentThreadID(prevThreadID);
954+
setCurrentPartialRenderer(prevPartialRenderer);
939955
}
940956
}
941957

0 commit comments

Comments
 (0)