Skip to content

Commit b96a651

Browse files
committed
Integrate some of the review feedback
1 parent 181e6a2 commit b96a651

File tree

2 files changed

+80
-102
lines changed

2 files changed

+80
-102
lines changed

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ export type EncodeFormActionCallback = <A>(
6363

6464
export type ServerReferenceId = any;
6565

66-
type ServerReferenceMetaData = {
66+
type ServerReferenceClosure = {
6767
id: ServerReferenceId,
68+
originalBind: Function,
6869
bound: null | Thenable<Array<any>>,
6970
};
7071

71-
const knownServerReferences: WeakMap<Function, ServerReferenceMetaData> =
72+
const knownServerReferences: WeakMap<Function, ServerReferenceClosure> =
7273
new WeakMap();
7374

7475
// Serializable values
@@ -763,16 +764,17 @@ export function processReply(
763764
}
764765

765766
if (typeof value === 'function') {
766-
const metaData = knownServerReferences.get(value);
767-
if (metaData !== undefined) {
768-
const metaDataJSON = JSON.stringify(metaData, resolveToJSON);
767+
const referenceClosure = knownServerReferences.get(value);
768+
if (referenceClosure !== undefined) {
769+
const {id, bound} = referenceClosure;
770+
const referenceClosureJSON = JSON.stringify({id, bound}, resolveToJSON);
769771
if (formData === null) {
770772
// Upgrade to use FormData to allow us to stream this value.
771773
formData = new FormData();
772774
}
773775
// The reference to this function came from the same client so we can pass it back.
774776
const refId = nextPartId++;
775-
formData.set(formFieldPrefix + refId, metaDataJSON);
777+
formData.set(formFieldPrefix + refId, referenceClosureJSON);
776778
return serializeServerReferenceID(refId);
777779
}
778780
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
@@ -867,7 +869,7 @@ export function processReply(
867869
}
868870

869871
const boundCache: WeakMap<
870-
ServerReferenceMetaData,
872+
ServerReferenceClosure,
871873
Thenable<FormData>,
872874
> = new WeakMap();
873875

@@ -908,21 +910,22 @@ function defaultEncodeFormAction(
908910
this: any => Promise<any>,
909911
identifierPrefix: string,
910912
): ReactCustomFormAction {
911-
const metaData = knownServerReferences.get(this);
912-
if (!metaData) {
913+
const referenceClosure = knownServerReferences.get(this);
914+
if (!referenceClosure) {
913915
throw new Error(
914916
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
915917
'This is a bug in React.',
916918
);
917919
}
918920
let data: null | FormData = null;
919921
let name;
920-
const boundPromise = metaData.bound;
922+
const boundPromise = referenceClosure.bound;
921923
if (boundPromise !== null) {
922-
let thenable = boundCache.get(metaData);
924+
let thenable = boundCache.get(referenceClosure);
923925
if (!thenable) {
924-
thenable = encodeFormData(metaData);
925-
boundCache.set(metaData, thenable);
926+
const {id, bound} = referenceClosure;
927+
thenable = encodeFormData({id, bound});
928+
boundCache.set(referenceClosure, thenable);
926929
}
927930
if (thenable.status === 'rejected') {
928931
throw thenable.reason;
@@ -944,7 +947,7 @@ function defaultEncodeFormAction(
944947
name = '$ACTION_REF_' + identifierPrefix;
945948
} else {
946949
// This is the simple case so we can just encode the ID.
947-
name = '$ACTION_ID_' + metaData.id;
950+
name = '$ACTION_ID_' + referenceClosure.id;
948951
}
949952
return {
950953
name: name,
@@ -959,38 +962,38 @@ function customEncodeFormAction(
959962
identifierPrefix: string,
960963
encodeFormAction: EncodeFormActionCallback,
961964
): ReactCustomFormAction {
962-
const metaData = knownServerReferences.get(reference);
963-
if (!metaData) {
965+
const referenceClosure = knownServerReferences.get(reference);
966+
if (!referenceClosure) {
964967
throw new Error(
965968
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
966969
'This is a bug in React.',
967970
);
968971
}
969-
let boundPromise: Promise<Array<any>> = (metaData.bound: any);
972+
let boundPromise: Promise<Array<any>> = (referenceClosure.bound: any);
970973
if (boundPromise === null) {
971974
boundPromise = Promise.resolve([]);
972975
}
973-
return encodeFormAction(metaData.id, boundPromise);
976+
return encodeFormAction(referenceClosure.id, boundPromise);
974977
}
975978

976979
function isSignatureEqual(
977980
this: any => Promise<any>,
978981
referenceId: ServerReferenceId,
979982
numberOfBoundArgs: number,
980983
): boolean {
981-
const metaData = knownServerReferences.get(this);
982-
if (!metaData) {
984+
const referenceClosure = knownServerReferences.get(this);
985+
if (!referenceClosure) {
983986
throw new Error(
984987
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
985988
'This is a bug in React.',
986989
);
987990
}
988-
if (metaData.id !== referenceId) {
991+
if (referenceClosure.id !== referenceId) {
989992
// These are different functions.
990993
return false;
991994
}
992995
// Now check if the number of bound arguments is the same.
993-
const boundPromise = metaData.bound;
996+
const boundPromise = referenceClosure.bound;
994997
if (boundPromise === null) {
995998
// No bound arguments.
996999
return numberOfBoundArgs === 0;
@@ -1134,6 +1137,16 @@ export function registerBoundServerReference<T: Function>(
11341137
bound: null | Thenable<Array<any>>,
11351138
encodeFormAction: void | EncodeFormActionCallback,
11361139
): void {
1140+
if (knownServerReferences.has(reference)) {
1141+
return;
1142+
}
1143+
1144+
knownServerReferences.set(reference, {
1145+
id,
1146+
originalBind: reference.bind,
1147+
bound,
1148+
});
1149+
11371150
// Expose encoder for use by SSR, as well as a special bind that can be used to
11381151
// keep server capabilities.
11391152
if (usedWithSSR) {
@@ -1154,86 +1167,74 @@ export function registerBoundServerReference<T: Function>(
11541167
Object.defineProperties((reference: any), {
11551168
$$FORM_ACTION: {value: $$FORM_ACTION},
11561169
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
1170+
bind: {value: bind},
11571171
});
1158-
defineBind(reference);
11591172
}
1160-
knownServerReferences.set(reference, {id, bound});
1161-
}
1162-
1163-
// TODO: Ideally we'd use `isServerReference` from
1164-
// 'react-server/src/ReactFlightServerConfig', but that can only be imported
1165-
// in a react-server environment.
1166-
function isServerReference(reference: Object): boolean {
1167-
return reference.$$typeof === Symbol.for('react.server.reference');
11681173
}
11691174

11701175
export function registerServerReference<T: Function>(
11711176
reference: T,
11721177
id: ServerReferenceId,
11731178
encodeFormAction?: EncodeFormActionCallback,
11741179
): ServerReference<T> {
1175-
const bound =
1176-
isServerReference(reference) && reference.$$bound
1177-
? Promise.resolve(reference.$$bound)
1178-
: null;
1179-
1180-
registerBoundServerReference(reference, id, bound, encodeFormAction);
1180+
registerBoundServerReference(reference, id, null, encodeFormAction);
11811181
return reference;
11821182
}
11831183

11841184
// $FlowFixMe[method-unbinding]
11851185
const FunctionBind = Function.prototype.bind;
11861186
// $FlowFixMe[method-unbinding]
11871187
const ArraySlice = Array.prototype.slice;
1188+
function bind(this: Function): Function {
1189+
const referenceClosure = knownServerReferences.get(this);
11881190

1189-
function defineBind<T: Function>(reference: T) {
1190-
// TODO: Instead of checking if it's a server reference, we could also use
1191-
// `reference.hasOwnProperty('bind')`.
1192-
const originalBind = isServerReference(reference)
1193-
? reference.bind
1194-
: FunctionBind;
1195-
1196-
function bind(): Function {
1191+
if (!referenceClosure) {
11971192
// $FlowFixMe[prop-missing]
1198-
const newFn = originalBind.apply(reference, arguments);
1199-
const metaData = knownServerReferences.get(reference);
1193+
return FunctionBind.apply(this, arguments);
1194+
}
12001195

1201-
if (metaData) {
1202-
if (__DEV__) {
1203-
const thisBind = arguments[0];
1204-
if (thisBind != null) {
1205-
// This doesn't warn in browser environments since it's not
1206-
// instrumented outside usedWithSSR. This makes this an SSR only
1207-
// warning which we don't generally do.
1208-
// TODO: Consider a DEV only instrumentation in the browser.
1209-
console.error(
1210-
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
1211-
);
1212-
}
1213-
}
1214-
const args = ArraySlice.call(arguments, 1);
1215-
let boundPromise = null;
1216-
if (metaData.bound !== null) {
1217-
boundPromise = Promise.resolve((metaData.bound: any)).then(boundArgs =>
1218-
boundArgs.concat(args),
1219-
);
1220-
} else {
1221-
boundPromise = Promise.resolve(args);
1222-
}
1223-
// Expose encoder for use by SSR, as well as a special bind that can be
1224-
// used to keep server capabilities.
1225-
Object.defineProperties((newFn: any), {
1226-
$$FORM_ACTION: {value: reference.$$FORM_ACTION},
1227-
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
1228-
});
1229-
defineBind(newFn);
1230-
knownServerReferences.set(newFn, {id: metaData.id, bound: boundPromise});
1196+
const newFn = referenceClosure.originalBind.apply(this, arguments);
1197+
1198+
if (__DEV__) {
1199+
const thisBind = arguments[0];
1200+
if (thisBind != null) {
1201+
// This doesn't warn in browser environments since it's not instrumented outside
1202+
// usedWithSSR. This makes this an SSR only warning which we don't generally do.
1203+
// TODO: Consider a DEV only instrumentation in the browser.
1204+
console.error(
1205+
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
1206+
);
12311207
}
1208+
}
1209+
1210+
const args = ArraySlice.call(arguments, 1);
1211+
let boundPromise = null;
1212+
if (referenceClosure.bound !== null) {
1213+
boundPromise = Promise.resolve((referenceClosure.bound: any)).then(
1214+
boundArgs => boundArgs.concat(args),
1215+
);
1216+
} else {
1217+
boundPromise = Promise.resolve(args);
1218+
}
1219+
1220+
knownServerReferences.set(newFn, {
1221+
id: referenceClosure.id,
1222+
originalBind: newFn.bind,
1223+
bound: boundPromise,
1224+
});
12321225

1233-
return newFn;
1226+
// Expose encoder for use by SSR, as well as a special bind that can be used to
1227+
// keep server capabilities.
1228+
if (usedWithSSR) {
1229+
// Only expose this in builds that would actually use it. Not needed on the client.
1230+
Object.defineProperties((newFn: any), {
1231+
$$FORM_ACTION: {value: this.$$FORM_ACTION},
1232+
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
1233+
bind: {value: bind},
1234+
});
12341235
}
12351236

1236-
Object.defineProperty((reference: any), 'bind', {value: bind});
1237+
return newFn;
12371238
}
12381239

12391240
export type FindSourceMapURLCallback = (

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -333,27 +333,4 @@ describe('ReactFlightDOMReplyEdge', () => {
333333
expect(replyResult.method).toBe(greet);
334334
expect(replyResult.boundMethod()).toBe('hi, there');
335335
});
336-
337-
it('can pass a bound registered server reference', async () => {
338-
function greet(greeting, name) {
339-
return greeting + ', ' + name;
340-
}
341-
const ServerModule = serverExports({
342-
greet,
343-
});
344-
345-
const boundGreet = ServerModule.greet.bind(null, 'hi');
346-
347-
ReactServerDOMClient.registerServerReference(boundGreet, boundGreet.$$id);
348-
349-
const body = await ReactServerDOMClient.encodeReply({
350-
method: boundGreet,
351-
boundMethod: boundGreet.bind(null, 'there'),
352-
});
353-
const replyResult = await ReactServerDOMServer.decodeReply(
354-
body,
355-
webpackServerMap,
356-
);
357-
expect(replyResult.boundMethod()).toBe('hi, there');
358-
});
359336
});

0 commit comments

Comments
 (0)