@samitouri / QOS-React / commits / 5d08a24c21

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++;