@@ -63,12 +63,13 @@ export type EncodeFormActionCallback = <A>(
63
63
64
64
export type ServerReferenceId = any ;
65
65
66
- type ServerReferenceMetaData = {
66
+ type ServerReferenceClosure = {
67
67
id : ServerReferenceId ,
68
+ originalBind : Function ,
68
69
bound : null | Thenable < Array < any >> ,
69
70
} ;
70
71
71
- const knownServerReferences: WeakMap< Function , ServerReferenceMetaData > =
72
+ const knownServerReferences: WeakMap< Function , ServerReferenceClosure > =
72
73
new WeakMap();
73
74
74
75
// Serializable values
@@ -763,16 +764,17 @@ export function processReply(
763
764
}
764
765
765
766
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 ) ;
769
771
if ( formData === null ) {
770
772
// Upgrade to use FormData to allow us to stream this value.
771
773
formData = new FormData ( ) ;
772
774
}
773
775
// The reference to this function came from the same client so we can pass it back.
774
776
const refId = nextPartId ++ ;
775
- formData . set ( formFieldPrefix + refId , metaDataJSON ) ;
777
+ formData . set ( formFieldPrefix + refId , referenceClosureJSON ) ;
776
778
return serializeServerReferenceID ( refId ) ;
777
779
}
778
780
if ( temporaryReferences !== undefined && key . indexOf ( ':' ) === - 1 ) {
@@ -867,7 +869,7 @@ export function processReply(
867
869
}
868
870
869
871
const boundCache : WeakMap <
870
- ServerReferenceMetaData ,
872
+ ServerReferenceClosure ,
871
873
Thenable < FormData > ,
872
874
> = new WeakMap ( ) ;
873
875
@@ -908,21 +910,22 @@ function defaultEncodeFormAction(
908
910
this : any => Promise < any > ,
909
911
identifierPrefix : string ,
910
912
) : ReactCustomFormAction {
911
- const metaData = knownServerReferences . get ( this ) ;
912
- if ( ! metaData ) {
913
+ const referenceClosure = knownServerReferences . get ( this ) ;
914
+ if ( ! referenceClosure ) {
913
915
throw new Error (
914
916
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
915
917
'This is a bug in React.' ,
916
918
) ;
917
919
}
918
920
let data : null | FormData = null ;
919
921
let name ;
920
- const boundPromise = metaData . bound ;
922
+ const boundPromise = referenceClosure . bound ;
921
923
if ( boundPromise !== null ) {
922
- let thenable = boundCache . get ( metaData ) ;
924
+ let thenable = boundCache . get ( referenceClosure ) ;
923
925
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 ) ;
926
929
}
927
930
if ( thenable . status === 'rejected' ) {
928
931
throw thenable . reason ;
@@ -944,7 +947,7 @@ function defaultEncodeFormAction(
944
947
name = '$ACTION_REF_' + identifierPrefix ;
945
948
} else {
946
949
// This is the simple case so we can just encode the ID.
947
- name = '$ACTION_ID_' + metaData . id ;
950
+ name = '$ACTION_ID_' + referenceClosure . id ;
948
951
}
949
952
return {
950
953
name : name ,
@@ -959,38 +962,38 @@ function customEncodeFormAction(
959
962
identifierPrefix : string ,
960
963
encodeFormAction : EncodeFormActionCallback ,
961
964
) : ReactCustomFormAction {
962
- const metaData = knownServerReferences . get ( reference ) ;
963
- if ( ! metaData ) {
965
+ const referenceClosure = knownServerReferences . get ( reference ) ;
966
+ if ( ! referenceClosure ) {
964
967
throw new Error (
965
968
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
966
969
'This is a bug in React.' ,
967
970
) ;
968
971
}
969
- let boundPromise : Promise < Array < any >> = ( metaData . bound : any ) ;
972
+ let boundPromise : Promise < Array < any >> = ( referenceClosure . bound : any ) ;
970
973
if ( boundPromise === null ) {
971
974
boundPromise = Promise . resolve ( [ ] ) ;
972
975
}
973
- return encodeFormAction(metaData .id, boundPromise);
976
+ return encodeFormAction(referenceClosure .id, boundPromise);
974
977
}
975
978
976
979
function isSignatureEqual (
977
980
this : any => Promise < any > ,
978
981
referenceId: ServerReferenceId,
979
982
numberOfBoundArgs: number,
980
983
): boolean {
981
- const metaData = knownServerReferences . get ( this ) ;
982
- if ( ! metaData ) {
984
+ const referenceClosure = knownServerReferences . get ( this ) ;
985
+ if ( ! referenceClosure ) {
983
986
throw new Error (
984
987
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
985
988
'This is a bug in React.' ,
986
989
) ;
987
990
}
988
- if ( metaData . id !== referenceId ) {
991
+ if ( referenceClosure . id !== referenceId ) {
989
992
// These are different functions.
990
993
return false ;
991
994
}
992
995
// Now check if the number of bound arguments is the same.
993
- const boundPromise = metaData . bound ;
996
+ const boundPromise = referenceClosure . bound ;
994
997
if ( boundPromise === null ) {
995
998
// No bound arguments.
996
999
return numberOfBoundArgs === 0 ;
@@ -1134,6 +1137,16 @@ export function registerBoundServerReference<T: Function>(
1134
1137
bound : null | Thenable < Array < any >> ,
1135
1138
encodeFormAction : void | EncodeFormActionCallback ,
1136
1139
) : void {
1140
+ if ( knownServerReferences . has ( reference ) ) {
1141
+ return ;
1142
+ }
1143
+
1144
+ knownServerReferences . set ( reference , {
1145
+ id,
1146
+ originalBind : reference . bind ,
1147
+ bound,
1148
+ } ) ;
1149
+
1137
1150
// Expose encoder for use by SSR, as well as a special bind that can be used to
1138
1151
// keep server capabilities.
1139
1152
if ( usedWithSSR ) {
@@ -1154,86 +1167,74 @@ export function registerBoundServerReference<T: Function>(
1154
1167
Object . defineProperties ( ( reference : any ) , {
1155
1168
$$FORM_ACTION : { value : $$FORM_ACTION } ,
1156
1169
$$IS_SIGNATURE_EQUAL : { value : isSignatureEqual } ,
1170
+ bind : { value : bind } ,
1157
1171
} ) ;
1158
- defineBind ( reference ) ;
1159
1172
}
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' ) ;
1168
1173
}
1169
1174
1170
1175
export function registerServerReference < T : Function > (
1171
1176
reference : T ,
1172
1177
id : ServerReferenceId ,
1173
1178
encodeFormAction ? : EncodeFormActionCallback ,
1174
1179
) : 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 ) ;
1181
1181
return reference ;
1182
1182
}
1183
1183
1184
1184
// $FlowFixMe[method-unbinding]
1185
1185
const FunctionBind = Function . prototype . bind ;
1186
1186
// $FlowFixMe[method-unbinding]
1187
1187
const ArraySlice = Array . prototype . slice ;
1188
+ function bind ( this : Function ) : Function {
1189
+ const referenceClosure = knownServerReferences . get ( this ) ;
1188
1190
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 ) {
1197
1192
// $FlowFixMe[prop-missing]
1198
- const newFn = originalBind . apply ( reference , arguments ) ;
1199
- const metaData = knownServerReferences . get ( reference ) ;
1193
+ return FunctionBind . apply ( this , arguments ) ;
1194
+ }
1200
1195
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
+ ) ;
1231
1207
}
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
+ } ) ;
1232
1225
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
+ } ) ;
1234
1235
}
1235
1236
1236
- Object . defineProperty ( ( reference : any ) , 'bind' , { value : bind } ) ;
1237
+ return newFn ;
1237
1238
}
1238
1239
1239
1240
export type FindSourceMapURLCallback = (
0 commit comments