@@ -63,10 +63,13 @@ export type EncodeFormActionCallback = <A>(
63
63
64
64
export type ServerReferenceId = any ;
65
65
66
- const knownServerReferences : WeakMap <
67
- Function ,
68
- { id : ServerReferenceId , bound : null | Thenable < Array < any >> } ,
69
- > = new WeakMap ( ) ;
66
+ type ServerReferenceMetaData = {
67
+ id : ServerReferenceId ,
68
+ bound : null | Thenable < Array < any >> ,
69
+ } ;
70
+
71
+ const knownServerReferences: WeakMap< Function , ServerReferenceMetaData > =
72
+ new WeakMap();
70
73
71
74
// Serializable values
72
75
export type ReactServerValue =
@@ -864,7 +867,7 @@ export function processReply(
864
867
}
865
868
866
869
const boundCache : WeakMap <
867
- { id : ServerReferenceId , bound : null | Thenable < Array < any > > } ,
870
+ ServerReferenceMetaData ,
868
871
Thenable < FormData > ,
869
872
> = new WeakMap ( ) ;
870
873
@@ -905,21 +908,21 @@ function defaultEncodeFormAction(
905
908
this : any => Promise < any > ,
906
909
identifierPrefix : string ,
907
910
) : ReactCustomFormAction {
908
- const reference = knownServerReferences . get ( this ) ;
909
- if ( ! reference ) {
911
+ const metaData = knownServerReferences . get ( this ) ;
912
+ if ( ! metaData ) {
910
913
throw new Error (
911
914
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
912
915
'This is a bug in React.' ,
913
916
) ;
914
917
}
915
918
let data : null | FormData = null ;
916
919
let name ;
917
- const boundPromise = reference . bound ;
920
+ const boundPromise = metaData . bound ;
918
921
if ( boundPromise !== null ) {
919
- let thenable = boundCache . get ( reference ) ;
922
+ let thenable = boundCache . get ( metaData ) ;
920
923
if ( ! thenable ) {
921
- thenable = encodeFormData ( reference ) ;
922
- boundCache . set ( reference , thenable ) ;
924
+ thenable = encodeFormData ( metaData ) ;
925
+ boundCache . set ( metaData , thenable ) ;
923
926
}
924
927
if ( thenable . status === 'rejected' ) {
925
928
throw thenable . reason ;
@@ -941,7 +944,7 @@ function defaultEncodeFormAction(
941
944
name = '$ACTION_REF_' + identifierPrefix ;
942
945
} else {
943
946
// This is the simple case so we can just encode the ID.
944
- name = '$ACTION_ID_' + reference . id ;
947
+ name = '$ACTION_ID_' + metaData . id ;
945
948
}
946
949
return {
947
950
name : name ,
@@ -952,42 +955,42 @@ function defaultEncodeFormAction(
952
955
}
953
956
954
957
function customEncodeFormAction (
955
- proxy : any => Promise < any > ,
958
+ reference : any => Promise < any > ,
956
959
identifierPrefix : string ,
957
960
encodeFormAction : EncodeFormActionCallback ,
958
961
) : ReactCustomFormAction {
959
- const reference = knownServerReferences . get ( proxy ) ;
960
- if ( ! reference ) {
962
+ const metaData = knownServerReferences . get ( reference ) ;
963
+ if ( ! metaData ) {
961
964
throw new Error (
962
965
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
963
966
'This is a bug in React.' ,
964
967
) ;
965
968
}
966
- let boundPromise : Promise < Array < any >> = ( reference . bound : any ) ;
969
+ let boundPromise : Promise < Array < any >> = ( metaData . bound : any ) ;
967
970
if ( boundPromise === null ) {
968
971
boundPromise = Promise . resolve ( [ ] ) ;
969
972
}
970
- return encodeFormAction(reference .id, boundPromise);
973
+ return encodeFormAction(metaData .id, boundPromise);
971
974
}
972
975
973
976
function isSignatureEqual (
974
977
this : any => Promise < any > ,
975
978
referenceId: ServerReferenceId,
976
979
numberOfBoundArgs: number,
977
980
): boolean {
978
- const reference = knownServerReferences . get ( this ) ;
979
- if ( ! reference ) {
981
+ const metaData = knownServerReferences . get ( this ) ;
982
+ if ( ! metaData ) {
980
983
throw new Error (
981
984
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
982
985
'This is a bug in React.' ,
983
986
) ;
984
987
}
985
- if ( reference . id !== referenceId ) {
988
+ if ( metaData . id !== referenceId ) {
986
989
// These are different functions.
987
990
return false ;
988
991
}
989
992
// Now check if the number of bound arguments is the same.
990
- const boundPromise = reference . bound ;
993
+ const boundPromise = metaData . bound ;
991
994
if ( boundPromise === null ) {
992
995
// No bound arguments.
993
996
return numberOfBoundArgs === 0 ;
@@ -1134,7 +1137,7 @@ export function registerBoundServerReference<T: Function>(
1134
1137
// Expose encoder for use by SSR, as well as a special bind that can be used to
1135
1138
// keep server capabilities.
1136
1139
if ( usedWithSSR ) {
1137
- // Only expose this in builds that would actually use it. Not needed on the client .
1140
+ // Only expose this in builds that would actually use it. Not needed in the browser .
1138
1141
const $$FORM_ACTION =
1139
1142
encodeFormAction === undefined
1140
1143
? defaultEncodeFormAction
@@ -1151,64 +1154,86 @@ export function registerBoundServerReference<T: Function>(
1151
1154
Object . defineProperties ( ( reference : any ) , {
1152
1155
$$FORM_ACTION : { value : $$FORM_ACTION } ,
1153
1156
$$IS_SIGNATURE_EQUAL : { value : isSignatureEqual } ,
1154
- bind : { value : bind } ,
1155
1157
} ) ;
1158
+ defineBind ( reference ) ;
1156
1159
}
1157
1160
knownServerReferences . set ( reference , { id, bound} ) ;
1158
1161
}
1159
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
+ }
1169
+
1160
1170
export function registerServerReference< T : Function > (
1161
1171
reference: T,
1162
1172
id: ServerReferenceId,
1163
1173
encodeFormAction?: EncodeFormActionCallback,
1164
1174
): ServerReference< T > {
1165
- registerBoundServerReference ( reference , id , null , encodeFormAction ) ;
1175
+ const bound =
1176
+ isServerReference ( reference ) && reference . $$bound
1177
+ ? Promise . resolve ( reference . $$bound )
1178
+ : null ;
1179
+
1180
+ registerBoundServerReference ( reference , id , bound , encodeFormAction ) ;
1166
1181
return reference ;
1167
1182
}
1168
1183
1169
1184
// $FlowFixMe[method-unbinding]
1170
1185
const FunctionBind = Function.prototype.bind;
1171
1186
// $FlowFixMe[method-unbinding]
1172
1187
const ArraySlice = Array.prototype.slice;
1173
- function bind ( this : Function ) : Function {
1174
- // $FlowFixMe[unsupported-syntax]
1175
- // $FlowFixMe[prop-missing]
1176
- const newFn = FunctionBind . apply ( this , arguments ) ;
1177
- const reference = knownServerReferences . get ( this ) ;
1178
- if ( reference ) {
1179
- if ( __DEV__ ) {
1180
- const thisBind = arguments [ 0 ] ;
1181
- if ( thisBind != null ) {
1182
- // This doesn't warn in browser environments since it's not instrumented outside
1183
- // usedWithSSR. This makes this an SSR only warning which we don't generally do.
1184
- // TODO: Consider a DEV only instrumentation in the browser.
1185
- console . error (
1186
- 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().' ,
1188
+
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 {
1197
+ // $FlowFixMe[prop-missing]
1198
+ const newFn = originalBind . apply ( reference , arguments ) ;
1199
+ const metaData = knownServerReferences . get ( reference ) ;
1200
+
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 ) ,
1187
1219
) ;
1220
+ } else {
1221
+ boundPromise = Promise . resolve ( args ) ;
1188
1222
}
1189
- }
1190
- const args = ArraySlice . call ( arguments , 1 ) ;
1191
- let boundPromise = null ;
1192
- if ( reference . bound !== null ) {
1193
- boundPromise = Promise . resolve ( ( reference . bound : any ) ) . then ( boundArgs =>
1194
- boundArgs . concat ( args ) ,
1195
- ) ;
1196
- } else {
1197
- boundPromise = Promise . resolve ( args ) ;
1198
- }
1199
- // Expose encoder for use by SSR, as well as a special bind that can be used to
1200
- // keep server capabilities.
1201
- if ( usedWithSSR ) {
1202
- // Only expose this in builds that would actually use it. Not needed on the client.
1223
+ // Expose encoder for use by SSR, as well as a special bind that can be
1224
+ // used to keep server capabilities.
1203
1225
Object . defineProperties ( ( newFn : any ) , {
1204
- $$FORM_ACTION : { value : this . $$FORM_ACTION } ,
1226
+ $$FORM_ACTION : { value : reference . $$FORM_ACTION } ,
1205
1227
$$IS_SIGNATURE_EQUAL : { value : isSignatureEqual } ,
1206
- bind : { value : bind } ,
1207
1228
} ) ;
1229
+ defineBind ( newFn ) ;
1230
+ knownServerReferences . set ( newFn , { id : metaData . id , bound : boundPromise } ) ;
1208
1231
}
1209
- knownServerReferences . set ( newFn , { id : reference . id , bound : boundPromise } ) ;
1232
+
1233
+ return newFn ;
1210
1234
}
1211
- return newFn ;
1235
+
1236
+ Object . defineProperty ( ( reference : any ) , 'bind' , { value : bind } ) ;
1212
1237
}
1213
1238
1214
1239
export type FindSourceMapURLCallback = (
0 commit comments