Skip to content

Commit

Permalink
Implement useOpaqueIdentifier (facebook#21260)
Browse files Browse the repository at this point in the history
The format of this ID is specific to the format.
  • Loading branch information
sebmarkbage authored and koto committed Jun 15, 2021
1 parent b15c6f8 commit 0c3fe58
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 7 deletions.
22 changes: 20 additions & 2 deletions packages/react-dom/src/server/ReactDOMServerFormatConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ export type ResponseState = {
placeholderPrefix: PrecomputedChunk,
segmentPrefix: PrecomputedChunk,
boundaryPrefix: string,
opaqueIdentifierPrefix: PrecomputedChunk,
opaqueIdentifierPrefix: string,
nextSuspenseID: number,
nextOpaqueID: number,
sentCompleteSegmentFunction: boolean,
sentCompleteBoundaryFunction: boolean,
sentClientRenderFunction: boolean,
Expand All @@ -72,8 +73,9 @@ export function createResponseState(
placeholderPrefix: stringToPrecomputedChunk(identifierPrefix + 'P:'),
segmentPrefix: stringToPrecomputedChunk(identifierPrefix + 'S:'),
boundaryPrefix: identifierPrefix + 'B:',
opaqueIdentifierPrefix: stringToPrecomputedChunk(identifierPrefix + 'R:'),
opaqueIdentifierPrefix: identifierPrefix + 'R:',
nextSuspenseID: 0,
nextOpaqueID: 0,
sentCompleteSegmentFunction: false,
sentCompleteBoundaryFunction: false,
sentClientRenderFunction: false,
Expand Down Expand Up @@ -172,6 +174,22 @@ export function createSuspenseBoundaryID(
return {formattedID: null};
}

export type OpaqueIDType = string;

export function makeServerID(
responseState: null | ResponseState,
): OpaqueIDType {
invariant(
responseState !== null,
'Invalid hook call. Hooks can only be called inside of the body of a function component.',
);
// TODO: This is not deterministic since it's created during render.
return (
responseState.opaqueIdentifierPrefix +
(responseState.nextOpaqueID++).toString(36)
);
}

function encodeHTMLTextNode(text: string): string {
return escapeTextForBrowser(text);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG;
// Per response,
export type ResponseState = {
nextSuspenseID: number,
nextOpaqueID: number,
};

// Allows us to keep track of what we've already written so we can refer back to it.
export function createResponseState(): ResponseState {
return {
nextSuspenseID: 0,
nextOpaqueID: 0,
};
}

Expand Down Expand Up @@ -108,6 +110,19 @@ export function createSuspenseBoundaryID(
return responseState.nextSuspenseID++;
}

export type OpaqueIDType = number;

export function makeServerID(
responseState: null | ResponseState,
): OpaqueIDType {
invariant(
responseState !== null,
'Invalid hook call. Hooks can only be called inside of the body of a function component.',
);
// TODO: This is not deterministic since it's created during render.
return responseState.nextOpaqueID++;
}

const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');

export function pushEmpty(
Expand Down
6 changes: 6 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type Destination = {

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

let opaqueID = 0;

const ReactNoopServer = ReactFizzServer({
scheduleWork(callback: () => void) {
callback();
Expand Down Expand Up @@ -84,6 +86,10 @@ const ReactNoopServer = ReactFizzServer({
return {state: 'pending', children: []};
},

makeServerID(): number {
return opaqueID++;
},

getChildFormatContext(): null {
return null;
},
Expand Down
15 changes: 12 additions & 3 deletions packages/react-server/src/ReactFizzHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import type {
ReactContext,
} from 'shared/ReactTypes';

import type {ResponseState, OpaqueIDType} from './ReactServerFormatConfig';

import {readContext as readContextImpl} from './ReactFizzNewContext';

import {makeServerID} from './ReactServerFormatConfig';

import invariant from 'shared/invariant';
import {enableCache} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
Expand All @@ -41,8 +45,6 @@ type Hook = {|
next: Hook | null,
|};

type OpaqueIDType = string;

let currentlyRenderingComponent: Object | null = null;
let firstWorkInProgressHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
Expand Down Expand Up @@ -474,7 +476,7 @@ function useTransition(): [(callback: () => void) => void, boolean] {
}

function useOpaqueIdentifier(): OpaqueIDType {
throw new Error('Not yet implemented.');
return makeServerID(currentResponseState);
}

function unsupportedRefresh() {
Expand Down Expand Up @@ -513,3 +515,10 @@ if (enableCache) {
Dispatcher.getCacheForType = getCacheForType;
Dispatcher.useCacheRefresh = useCacheRefresh;
}

export let currentResponseState: null | ResponseState = (null: any);
export function setCurrentResponseState(
responseState: null | ResponseState,
): void {
currentResponseState = responseState;
}
6 changes: 5 additions & 1 deletion packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ import {
finishHooks,
resetHooksState,
Dispatcher,
currentResponseState,
setCurrentResponseState,
} from './ReactFizzHooks';

import {
Expand Down Expand Up @@ -1341,7 +1343,8 @@ function performWork(request: Request): void {
const prevContext = getActiveContext();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = Dispatcher;

const prevResponseState = currentResponseState;
setCurrentResponseState(request.responseState);
try {
const pingedTasks = request.pingedTasks;
let i;
Expand All @@ -1357,6 +1360,7 @@ function performWork(request: Request): void {
reportError(request, error);
fatalError(request, error);
} finally {
setCurrentResponseState(prevResponseState);
ReactCurrentDispatcher.current = prevDispatcher;
if (prevDispatcher === Dispatcher) {
// This means that we were in a reentrant work loop. This could happen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ export opaque type Destination = mixed; // eslint-disable-line no-undef
export opaque type ResponseState = mixed;
export opaque type FormatContext = mixed;
export opaque type SuspenseBoundaryID = mixed;
export opaque type OpaqueIDType = mixed;

export const isPrimaryRenderer = false;

export const getChildFormatContext = $$$hostConfig.getChildFormatContext;
export const createSuspenseBoundaryID = $$$hostConfig.createSuspenseBoundaryID;
export const makeServerID = $$$hostConfig.makeServerID;
export const pushEmpty = $$$hostConfig.pushEmpty;
export const pushTextInstance = $$$hostConfig.pushTextInstance;
export const pushStartInstance = $$$hostConfig.pushStartInstance;
Expand Down
3 changes: 2 additions & 1 deletion scripts/error-codes/codes.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,5 +391,6 @@
"400": "menuitems cannot have `children` nor `dangerouslySetInnerHTML`.",
"401": "The stacks must reach the root at the same time. This is a bug in React.",
"402": "The depth must equal at least at zero before reaching the root. This is a bug in React.",
"403": "Tried to pop a Context at the root of the app. This is a bug in React."
"403": "Tried to pop a Context at the root of the app. This is a bug in React.",
"404": "Invalid hook call. Hooks can only be called inside of the body of a function component."
}

0 comments on commit 0c3fe58

Please sign in to comment.