@samitouri / QOS-React-2 / commits / f4d7a0f1ea

Implement useOpaqueIdentifier (#21260)

The format of this ID is specific to the format.

Sebastian Markbåge committed Apr 14, 2021 at 17:25 UTC f4d7a0f1ea0cd24e1979a9be6b2778b98cef414c
7 files changed +62 -7
packages/react-dom/src/server/ReactDOMServerFormatConfig.js
+20 -2
@@ -57,8 +57,9 @@ export type ResponseState = {
57 placeholderPrefix: PrecomputedChunk,
58 segmentPrefix: PrecomputedChunk,
59 boundaryPrefix: string,
60 - opaqueIdentifierPrefix: PrecomputedChunk,
60 + opaqueIdentifierPrefix: string,
61 nextSuspenseID: number,
62 + nextOpaqueID: number,
63 sentCompleteSegmentFunction: boolean,
64 sentCompleteBoundaryFunction: boolean,
65 sentClientRenderFunction: boolean,
@@ -72,8 +73,9 @@ export function createResponseState(
73 placeholderPrefix: stringToPrecomputedChunk(identifierPrefix + 'P:'),
74 segmentPrefix: stringToPrecomputedChunk(identifierPrefix + 'S:'),
75 boundaryPrefix: identifierPrefix + 'B:',
75 - opaqueIdentifierPrefix: stringToPrecomputedChunk(identifierPrefix + 'R:'),
76 + opaqueIdentifierPrefix: identifierPrefix + 'R:',
77 nextSuspenseID: 0,
78 + nextOpaqueID: 0,
79 sentCompleteSegmentFunction: false,
80 sentCompleteBoundaryFunction: false,
81 sentClientRenderFunction: false,
@@ -172,6 +174,22 @@ export function createSuspenseBoundaryID(
174 return {formattedID: null};
175 }
176
177 +export type OpaqueIDType = string;
178 +
179 +export function makeServerID(
180 + responseState: null | ResponseState,
181 +): OpaqueIDType {
182 + invariant(
183 + responseState !== null,
184 + 'Invalid hook call. Hooks can only be called inside of the body of a function component.',
185 + );
186 + // TODO: This is not deterministic since it's created during render.
187 + return (
188 + responseState.opaqueIdentifierPrefix +
189 + (responseState.nextOpaqueID++).toString(36)
190 + );
191 +}
192 +
193 function encodeHTMLTextNode(text: string): string {
194 return escapeTextForBrowser(text);
195 }
packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js
+15
@@ -61,12 +61,14 @@ SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG;
61 // Per response,
62 export type ResponseState = {
63 nextSuspenseID: number,
64 + nextOpaqueID: number,
65 };
66
67 // Allows us to keep track of what we've already written so we can refer back to it.
68 export function createResponseState(): ResponseState {
69 return {
70 nextSuspenseID: 0,
71 + nextOpaqueID: 0,
72 };
73 }
74
@@ -108,6 +110,19 @@ export function createSuspenseBoundaryID(
110 return responseState.nextSuspenseID++;
111 }
112
113 +export type OpaqueIDType = number;
114 +
115 +export function makeServerID(
116 + responseState: null | ResponseState,
117 +): OpaqueIDType {
118 + invariant(
119 + responseState !== null,
120 + 'Invalid hook call. Hooks can only be called inside of the body of a function component.',
121 + );
122 + // TODO: This is not deterministic since it's created during render.
123 + return responseState.nextOpaqueID++;
124 +}
125 +
126 const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
127
128 export function pushEmpty(
packages/react-noop-renderer/src/ReactNoopServer.js
+6
@@ -53,6 +53,8 @@ type Destination = {
53
54 const POP = Buffer.from('/', 'utf8');
55
56 +let opaqueID = 0;
57 +
58 const ReactNoopServer = ReactFizzServer({
59 scheduleWork(callback: () => void) {
60 callback();
@@ -84,6 +86,10 @@ const ReactNoopServer = ReactFizzServer({
86 return {state: 'pending', children: []};
87 },
88
89 + makeServerID(): number {
90 + return opaqueID++;
91 + },
92 +
93 getChildFormatContext(): null {
94 return null;
95 },
packages/react-server/src/ReactFizzHooks.js
+12 -3
@@ -16,8 +16,12 @@ import type {
16 ReactContext,
17 } from 'shared/ReactTypes';
18
19 +import type {ResponseState, OpaqueIDType} from './ReactServerFormatConfig';
20 +
21 import {readContext as readContextImpl} from './ReactFizzNewContext';
22
23 +import {makeServerID} from './ReactServerFormatConfig';
24 +
25 import invariant from 'shared/invariant';
26 import {enableCache} from 'shared/ReactFeatureFlags';
27 import is from 'shared/objectIs';
@@ -41,8 +45,6 @@ type Hook = {|
45 next: Hook | null,
46 |};
47
44 -type OpaqueIDType = string;
45 -
48 let currentlyRenderingComponent: Object | null = null;
49 let firstWorkInProgressHook: Hook | null = null;
50 let workInProgressHook: Hook | null = null;
@@ -474,7 +476,7 @@ function useTransition(): [(callback: () => void) => void, boolean] {
476 }
477
478 function useOpaqueIdentifier(): OpaqueIDType {
477 - throw new Error('Not yet implemented.');
479 + return makeServerID(currentResponseState);
480 }
481
482 function unsupportedRefresh() {
@@ -513,3 +515,10 @@ if (enableCache) {
515 Dispatcher.getCacheForType = getCacheForType;
516 Dispatcher.useCacheRefresh = useCacheRefresh;
517 }
518 +
519 +export let currentResponseState: null | ResponseState = (null: any);
520 +export function setCurrentResponseState(
521 + responseState: null | ResponseState,
522 +): void {
523 + currentResponseState = responseState;
524 +}
packages/react-server/src/ReactFizzServer.js
+5 -1
@@ -74,6 +74,8 @@ import {
74 finishHooks,
75 resetHooksState,
76 Dispatcher,
77 + currentResponseState,
78 + setCurrentResponseState,
79 } from './ReactFizzHooks';
80
81 import {
@@ -1341,7 +1343,8 @@ function performWork(request: Request): void {
1343 const prevContext = getActiveContext();
1344 const prevDispatcher = ReactCurrentDispatcher.current;
1345 ReactCurrentDispatcher.current = Dispatcher;
1344 -
1346 + const prevResponseState = currentResponseState;
1347 + setCurrentResponseState(request.responseState);
1348 try {
1349 const pingedTasks = request.pingedTasks;
1350 let i;
@@ -1357,6 +1360,7 @@ function performWork(request: Request): void {
1360 reportError(request, error);
1361 fatalError(request, error);
1362 } finally {
1363 + setCurrentResponseState(prevResponseState);
1364 ReactCurrentDispatcher.current = prevDispatcher;
1365 if (prevDispatcher === Dispatcher) {
1366 // This means that we were in a reentrant work loop. This could happen
packages/react-server/src/forks/ReactServerFormatConfig.custom.js
+2
@@ -28,11 +28,13 @@ export opaque type Destination = mixed; // eslint-disable-line no-undef
28 export opaque type ResponseState = mixed;
29 export opaque type FormatContext = mixed;
30 export opaque type SuspenseBoundaryID = mixed;
31 +export opaque type OpaqueIDType = mixed;
32
33 export const isPrimaryRenderer = false;
34
35 export const getChildFormatContext = $$$hostConfig.getChildFormatContext;
36 export const createSuspenseBoundaryID = $$$hostConfig.createSuspenseBoundaryID;
37 +export const makeServerID = $$$hostConfig.makeServerID;
38 export const pushEmpty = $$$hostConfig.pushEmpty;
39 export const pushTextInstance = $$$hostConfig.pushTextInstance;
40 export const pushStartInstance = $$$hostConfig.pushStartInstance;
scripts/error-codes/codes.json
+2 -1
@@ -391,5 +391,6 @@
391 "400": "menuitems cannot have `children` nor `dangerouslySetInnerHTML`.",
392 "401": "The stacks must reach the root at the same time. This is a bug in React.",
393 "402": "The depth must equal at least at zero before reaching the root. This is a bug in React.",
394 - "403": "Tried to pop a Context at the root of the app. This is a bug in React."
394 + "403": "Tried to pop a Context at the root of the app. This is a bug in React.",
395 + "404": "Invalid hook call. Hooks can only be called inside of the body of a function component."
396 }