useId: Use 'H' to separate main id from hook index (#23363)
No id should be a subset of any other id. Currently, this is not true when there are multiple hooks in the same component. We append the hook index to the end of the id, except for the first one. So you get this pattern. Before this change: - 1st hook's id: :R0: - 2nd hook's id: :R0:1: The first hook's id is a subset of all the other ids in the same component. The fix for this is to use a different character to separate the main id from the hook index. I've chosen a captial 'H' for this because capital letters are not part of the base 32 character set when encoding with `toString(32)`. After this change: - 1st hook's id: :R0: - 2nd hook's id: :R0H1:
Andrew Clark committed
Feb 25, 2022 at 15:46 UTC
5d08a24c21dcb3d42ea628ce3f94bdbd0d432756
4 files changed
+13
-9
packages/react-dom/src/__tests__/ReactDOMUseId-test.js
+2
-2
@@ -93,7 +93,7 @@ describe('useId', () => {
93
}
94
95
function normalizeTreeIdForTesting(id) {
96
- const result = id.match(/:(R|r)(.*):(([0-9]*):)?/);
96
+ const result = id.match(/:(R|r)([a-z0-9]*)(H([0-9]*))?:/);
97
if (result === undefined) {
98
throw new Error('Invalid id format');
99
}
@@ -342,7 +342,7 @@ describe('useId', () => {
342
<div
343
id="container"
344
>
345
- :R0:, :R0:1:, :R0:2:
345
+ :R0:, :R0H1:, :R0H2:
346
<!-- -->
347
</div>
348
`);
packages/react-dom/src/server/ReactDOMServerFormatConfig.js
+3
-3
@@ -242,16 +242,16 @@ export function makeId(
242
): string {
243
const idPrefix = responseState.idPrefix;
244
245
- let id = ':' + idPrefix + 'R' + treeId + ':';
245
+ let id = ':' + idPrefix + 'R' + treeId;
246
247
// Unless this is the first id at this level, append a number at the end
248
// that represents the position of this useId hook among all the useId
249
// hooks for this fiber.
250
if (localId > 0) {
251
- id += localId.toString(32) + ':';
251
+ id += 'H' + localId.toString(32);
252
}
253
254
- return id;
254
+ return id + ':';
255
}
256
257
function encodeHTMLTextNode(text: string): string {
packages/react-reconciler/src/ReactFiberHooks.new.js
+4
-2
@@ -2072,15 +2072,17 @@ function mountId(): string {
2072
const treeId = getTreeId();
2073
2074
// Use a captial R prefix for server-generated ids.
2075
- id = ':' + identifierPrefix + 'R' + treeId + ':';
2075
+ id = ':' + identifierPrefix + 'R' + treeId;
2076
2077
// Unless this is the first id at this level, append a number at the end
2078
// that represents the position of this useId hook among all the useId
2079
// hooks for this fiber.
2080
const localId = localIdCounter++;
2081
if (localId > 0) {
2082
- id += localId.toString(32) + ':';
2082
+ id += 'H' + localId.toString(32);
2083
}
2084
+
2085
+ id += ':';
2086
} else {
2087
// Use a lowercase r prefix for client-generated ids.
2088
const globalClientId = globalClientIdCounter++;
packages/react-reconciler/src/ReactFiberHooks.old.js
+4
-2
@@ -2072,15 +2072,17 @@ function mountId(): string {
2072
const treeId = getTreeId();
2073
2074
// Use a captial R prefix for server-generated ids.
2075
- id = ':' + identifierPrefix + 'R' + treeId + ':';
2075
+ id = ':' + identifierPrefix + 'R' + treeId;
2076
2077
// Unless this is the first id at this level, append a number at the end
2078
// that represents the position of this useId hook among all the useId
2079
// hooks for this fiber.
2080
const localId = localIdCounter++;
2081
if (localId > 0) {
2082
- id += localId.toString(32) + ':';
2082
+ id += 'H' + localId.toString(32);
2083
}
2084
+
2085
+ id += ':';
2086
} else {
2087
// Use a lowercase r prefix for client-generated ids.
2088
const globalClientId = globalClientIdCounter++;