Skip to content

Commit dfb7f89

Browse files
committedApr 14, 2021
Implement useOpaqueIdentifier
The format of this ID is specific to the format.
1 parent b37888c commit dfb7f89

File tree

7 files changed

+61
-7
lines changed

7 files changed

+61
-7
lines changed
 

‎packages/react-dom/src/server/ReactDOMServerFormatConfig.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ export type ResponseState = {
5757
placeholderPrefix: PrecomputedChunk,
5858
segmentPrefix: PrecomputedChunk,
5959
boundaryPrefix: string,
60-
opaqueIdentifierPrefix: PrecomputedChunk,
60+
opaqueIdentifierPrefix: string,
6161
nextSuspenseID: number,
62+
nextOpaqueID: number,
6263
sentCompleteSegmentFunction: boolean,
6364
sentCompleteBoundaryFunction: boolean,
6465
sentClientRenderFunction: boolean,
@@ -72,8 +73,9 @@ export function createResponseState(
7273
placeholderPrefix: stringToPrecomputedChunk(identifierPrefix + 'P:'),
7374
segmentPrefix: stringToPrecomputedChunk(identifierPrefix + 'S:'),
7475
boundaryPrefix: identifierPrefix + 'B:',
75-
opaqueIdentifierPrefix: stringToPrecomputedChunk(identifierPrefix + 'R:'),
76+
opaqueIdentifierPrefix: identifierPrefix + 'R:',
7677
nextSuspenseID: 0,
78+
nextOpaqueID: 0,
7779
sentCompleteSegmentFunction: false,
7880
sentCompleteBoundaryFunction: false,
7981
sentClientRenderFunction: false,
@@ -172,6 +174,21 @@ export function createSuspenseBoundaryID(
172174
return {formattedID: null};
173175
}
174176

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+
return (
187+
responseState.opaqueIdentifierPrefix +
188+
(responseState.nextOpaqueID++).toString(36)
189+
);
190+
}
191+
175192
function encodeHTMLTextNode(text: string): string {
176193
return escapeTextForBrowser(text);
177194
}

‎packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js

+15
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,14 @@ SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG;
6161
// Per response,
6262
export type ResponseState = {
6363
nextSuspenseID: number,
64+
nextOpaqueID: number,
6465
};
6566

6667
// Allows us to keep track of what we've already written so we can refer back to it.
6768
export function createResponseState(): ResponseState {
6869
return {
6970
nextSuspenseID: 0,
71+
nextOpaqueID: 0,
7072
};
7173
}
7274

@@ -108,6 +110,19 @@ export function createSuspenseBoundaryID(
108110
return responseState.nextSuspenseID++;
109111
}
110112

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+
111126
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
112127

113128
export function pushEmpty(

‎packages/react-noop-renderer/src/ReactNoopServer.js

+6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Destination = {
5353

5454
const POP = Buffer.from('/', 'utf8');
5555

56+
let opaqueID = 0;
57+
5658
const ReactNoopServer = ReactFizzServer({
5759
scheduleWork(callback: () => void) {
5860
callback();
@@ -84,6 +86,10 @@ const ReactNoopServer = ReactFizzServer({
8486
return {state: 'pending', children: []};
8587
},
8688

89+
makeServerID(): number {
90+
return opaqueID++;
91+
},
92+
8793
getChildFormatContext(): null {
8894
return null;
8995
},

‎packages/react-server/src/ReactFizzHooks.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ import type {
1616
ReactContext,
1717
} from 'shared/ReactTypes';
1818

19+
import type {ResponseState, OpaqueIDType} from './ReactServerFormatConfig';
20+
1921
import {readContext as readContextImpl} from './ReactFizzNewContext';
2022

23+
import {makeServerID} from './ReactServerFormatConfig';
24+
2125
import invariant from 'shared/invariant';
2226
import {enableCache} from 'shared/ReactFeatureFlags';
2327
import is from 'shared/objectIs';
@@ -41,8 +45,6 @@ type Hook = {|
4145
next: Hook | null,
4246
|};
4347

44-
type OpaqueIDType = string;
45-
4648
let currentlyRenderingComponent: Object | null = null;
4749
let firstWorkInProgressHook: Hook | null = null;
4850
let workInProgressHook: Hook | null = null;
@@ -474,7 +476,7 @@ function useTransition(): [(callback: () => void) => void, boolean] {
474476
}
475477

476478
function useOpaqueIdentifier(): OpaqueIDType {
477-
throw new Error('Not yet implemented.');
479+
return makeServerID(currentResponseState);
478480
}
479481

480482
function unsupportedRefresh() {
@@ -513,3 +515,10 @@ if (enableCache) {
513515
Dispatcher.getCacheForType = getCacheForType;
514516
Dispatcher.useCacheRefresh = useCacheRefresh;
515517
}
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
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ import {
7474
finishHooks,
7575
resetHooksState,
7676
Dispatcher,
77+
currentResponseState,
78+
setCurrentResponseState,
7779
} from './ReactFizzHooks';
7880

7981
import {
@@ -1334,7 +1336,8 @@ function performWork(request: Request): void {
13341336
const prevContext = getActiveContext();
13351337
const prevDispatcher = ReactCurrentDispatcher.current;
13361338
ReactCurrentDispatcher.current = Dispatcher;
1337-
1339+
const prevResponseState = currentResponseState;
1340+
setCurrentResponseState(request.responseState);
13381341
try {
13391342
const pingedTasks = request.pingedTasks;
13401343
let i;
@@ -1350,6 +1353,7 @@ function performWork(request: Request): void {
13501353
reportError(request, error);
13511354
fatalError(request, error);
13521355
} finally {
1356+
setCurrentResponseState(prevResponseState);
13531357
ReactCurrentDispatcher.current = prevDispatcher;
13541358
if (prevDispatcher === Dispatcher) {
13551359
// This means that we were in a reentrant work loop. This could happen

‎packages/react-server/src/forks/ReactServerFormatConfig.custom.js

+2
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ export opaque type Destination = mixed; // eslint-disable-line no-undef
2828
export opaque type ResponseState = mixed;
2929
export opaque type FormatContext = mixed;
3030
export opaque type SuspenseBoundaryID = mixed;
31+
export opaque type OpaqueIDType = mixed;
3132

3233
export const isPrimaryRenderer = false;
3334

3435
export const getChildFormatContext = $$$hostConfig.getChildFormatContext;
3536
export const createSuspenseBoundaryID = $$$hostConfig.createSuspenseBoundaryID;
37+
export const makeServerID = $$$hostConfig.makeServerID;
3638
export const pushEmpty = $$$hostConfig.pushEmpty;
3739
export const pushTextInstance = $$$hostConfig.pushTextInstance;
3840
export const pushStartInstance = $$$hostConfig.pushStartInstance;

‎scripts/error-codes/codes.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -391,5 +391,6 @@
391391
"400": "menuitems cannot have `children` nor `dangerouslySetInnerHTML`.",
392392
"401": "The stacks must reach the root at the same time. This is a bug in React.",
393393
"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."
395396
}

0 commit comments

Comments
 (0)