@@ -63,10 +63,13 @@ export type EncodeFormActionCallback = <A>(
6363
6464export type ServerReferenceId = any ;
6565
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();
7073
7174// Serializable values
7275export type ReactServerValue =
@@ -864,7 +867,7 @@ export function processReply(
864867}
865868
866869const boundCache : WeakMap <
867- { id : ServerReferenceId , bound : null | Thenable < Array < any > > } ,
870+ ServerReferenceMetaData ,
868871 Thenable < FormData > ,
869872> = new WeakMap ( ) ;
870873
@@ -905,21 +908,21 @@ function defaultEncodeFormAction(
905908 this : any => Promise < any > ,
906909 identifierPrefix : string ,
907910) : ReactCustomFormAction {
908- const reference = knownServerReferences . get ( this ) ;
909- if ( ! reference ) {
911+ const metaData = knownServerReferences . get ( this ) ;
912+ if ( ! metaData ) {
910913 throw new Error (
911914 'Tried to encode a Server Action from a different instance than the encoder is from. ' +
912915 'This is a bug in React.' ,
913916 ) ;
914917 }
915918 let data : null | FormData = null ;
916919 let name ;
917- const boundPromise = reference . bound ;
920+ const boundPromise = metaData . bound ;
918921 if ( boundPromise !== null ) {
919- let thenable = boundCache . get ( reference ) ;
922+ let thenable = boundCache . get ( metaData ) ;
920923 if ( ! thenable ) {
921- thenable = encodeFormData ( reference ) ;
922- boundCache . set ( reference , thenable ) ;
924+ thenable = encodeFormData ( metaData ) ;
925+ boundCache . set ( metaData , thenable ) ;
923926 }
924927 if ( thenable . status === 'rejected' ) {
925928 throw thenable . reason ;
@@ -941,7 +944,7 @@ function defaultEncodeFormAction(
941944 name = '$ACTION_REF_' + identifierPrefix ;
942945 } else {
943946 // This is the simple case so we can just encode the ID.
944- name = '$ACTION_ID_' + reference . id ;
947+ name = '$ACTION_ID_' + metaData . id ;
945948 }
946949 return {
947950 name : name ,
@@ -952,42 +955,42 @@ function defaultEncodeFormAction(
952955}
953956
954957function customEncodeFormAction (
955- proxy : any => Promise < any > ,
958+ reference : any => Promise < any > ,
956959 identifierPrefix : string ,
957960 encodeFormAction : EncodeFormActionCallback ,
958961) : ReactCustomFormAction {
959- const reference = knownServerReferences . get ( proxy ) ;
960- if ( ! reference ) {
962+ const metaData = knownServerReferences . get ( reference ) ;
963+ if ( ! metaData ) {
961964 throw new Error (
962965 'Tried to encode a Server Action from a different instance than the encoder is from. ' +
963966 'This is a bug in React.' ,
964967 ) ;
965968 }
966- let boundPromise : Promise < Array < any >> = ( reference . bound : any ) ;
969+ let boundPromise : Promise < Array < any >> = ( metaData . bound : any ) ;
967970 if ( boundPromise === null ) {
968971 boundPromise = Promise . resolve ( [ ] ) ;
969972 }
970- return encodeFormAction(reference .id, boundPromise);
973+ return encodeFormAction(metaData .id, boundPromise);
971974}
972975
973976function isSignatureEqual (
974977 this : any => Promise < any > ,
975978 referenceId: ServerReferenceId,
976979 numberOfBoundArgs: number,
977980): boolean {
978- const reference = knownServerReferences . get ( this ) ;
979- if ( ! reference ) {
981+ const metaData = knownServerReferences . get ( this ) ;
982+ if ( ! metaData ) {
980983 throw new Error (
981984 'Tried to encode a Server Action from a different instance than the encoder is from. ' +
982985 'This is a bug in React.' ,
983986 ) ;
984987 }
985- if ( reference . id !== referenceId ) {
988+ if ( metaData . id !== referenceId ) {
986989 // These are different functions.
987990 return false ;
988991 }
989992 // Now check if the number of bound arguments is the same.
990- const boundPromise = reference . bound ;
993+ const boundPromise = metaData . bound ;
991994 if ( boundPromise === null ) {
992995 // No bound arguments.
993996 return numberOfBoundArgs === 0 ;
@@ -1134,7 +1137,7 @@ export function registerBoundServerReference<T: Function>(
11341137 // Expose encoder for use by SSR, as well as a special bind that can be used to
11351138 // keep server capabilities.
11361139 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 .
11381141 const $$FORM_ACTION =
11391142 encodeFormAction === undefined
11401143 ? defaultEncodeFormAction
@@ -1151,64 +1154,86 @@ export function registerBoundServerReference<T: Function>(
11511154 Object . defineProperties ( ( reference : any ) , {
11521155 $$FORM_ACTION : { value : $$FORM_ACTION } ,
11531156 $$IS_SIGNATURE_EQUAL : { value : isSignatureEqual } ,
1154- bind : { value : bind } ,
11551157 } ) ;
1158+ defineBind ( reference ) ;
11561159 }
11571160 knownServerReferences . set ( reference , { id, bound} ) ;
11581161}
11591162
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+
11601170export function registerServerReference< T : Function > (
11611171 reference: T,
11621172 id: ServerReferenceId,
11631173 encodeFormAction?: EncodeFormActionCallback,
11641174): 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 ) ;
11661181 return reference ;
11671182}
11681183
11691184// $FlowFixMe[method-unbinding]
11701185const FunctionBind = Function.prototype.bind;
11711186// $FlowFixMe[method-unbinding]
11721187const 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 ) ,
11871219 ) ;
1220+ } else {
1221+ boundPromise = Promise . resolve ( args ) ;
11881222 }
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.
12031225 Object . defineProperties ( ( newFn : any ) , {
1204- $$FORM_ACTION : { value : this . $$FORM_ACTION } ,
1226+ $$FORM_ACTION : { value : reference . $$FORM_ACTION } ,
12051227 $$IS_SIGNATURE_EQUAL : { value : isSignatureEqual } ,
1206- bind : { value : bind } ,
12071228 } ) ;
1229+ defineBind ( newFn ) ;
1230+ knownServerReferences . set ( newFn , { id : metaData . id , bound : boundPromise } ) ;
12081231 }
1209- knownServerReferences . set ( newFn , { id : reference . id , bound : boundPromise } ) ;
1232+
1233+ return newFn ;
12101234 }
1211- return newFn ;
1235+
1236+ Object . defineProperty ( ( reference : any ) , 'bind' , { value : bind } ) ;
12121237}
12131238
12141239export type FindSourceMapURLCallback = (
0 commit comments