Skip to content

Commit 9bab5a2

Browse files
committed
Allow custom encoding of the form action
1 parent 7a32d71 commit 9bab5a2

File tree

10 files changed

+110
-16
lines changed

10 files changed

+110
-16
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import type {
2828
HintModel,
2929
} from 'react-server/src/ReactFlightServerConfig';
3030

31-
import type {CallServerCallback} from './ReactFlightReplyClient';
31+
import type {
32+
CallServerCallback,
33+
EncodeFormActionCallback,
34+
} from './ReactFlightReplyClient';
3235

3336
import type {Postpone} from 'react/src/ReactPostpone';
3437

@@ -53,7 +56,7 @@ import {
5356
REACT_POSTPONE_TYPE,
5457
} from 'shared/ReactSymbols';
5558

56-
export type {CallServerCallback};
59+
export type {CallServerCallback, EncodeFormActionCallback};
5760

5861
type UninitializedModel = string;
5962

@@ -206,6 +209,7 @@ export type Response = {
206209
_bundlerConfig: SSRModuleMap,
207210
_moduleLoading: ModuleLoading,
208211
_callServer: CallServerCallback,
212+
_encodeFormAction: void | EncodeFormActionCallback,
209213
_nonce: ?string,
210214
_chunks: Map<number, SomeChunk<any>>,
211215
_fromJSON: (key: string, value: JSONValue) => any,
@@ -592,7 +596,7 @@ function createServerReferenceProxy<A: Iterable<any>, T>(
592596
},
593597
);
594598
};
595-
registerServerReference(proxy, metaData);
599+
registerServerReference(proxy, metaData, response._encodeFormAction);
596600
return proxy;
597601
}
598602

@@ -785,13 +789,15 @@ export function createResponse(
785789
bundlerConfig: SSRModuleMap,
786790
moduleLoading: ModuleLoading,
787791
callServer: void | CallServerCallback,
792+
encodeFormAction: void | EncodeFormActionCallback,
788793
nonce: void | string,
789794
): Response {
790795
const chunks: Map<number, SomeChunk<any>> = new Map();
791796
const response: Response = {
792797
_bundlerConfig: bundlerConfig,
793798
_moduleLoading: moduleLoading,
794799
_callServer: callServer !== undefined ? callServer : missingCall,
800+
_encodeFormAction: encodeFormAction,
795801
_nonce: nonce,
796802
_chunks: chunks,
797803
_stringDecoder: createStringDecoder(),

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export opaque type ServerReference<T> = T;
4747

4848
export type CallServerCallback = <A, T>(id: any, args: A) => Promise<T>;
4949

50+
export type EncodeFormActionCallback = <A>(
51+
id: any,
52+
args: Promise<A>,
53+
) => ReactCustomFormAction;
54+
5055
export type ServerReferenceId = any;
5156

5257
const knownServerReferences: WeakMap<
@@ -454,7 +459,7 @@ function encodeFormData(reference: any): Thenable<FormData> {
454459
return thenable;
455460
}
456461

457-
export function encodeFormAction(
462+
function defaultEncodeFormAction(
458463
this: any => Promise<any>,
459464
identifierPrefix: string,
460465
): ReactCustomFormAction {
@@ -503,6 +508,25 @@ export function encodeFormAction(
503508
};
504509
}
505510

511+
function customEncodeFormAction(
512+
proxy: any => Promise<any>,
513+
identifierPrefix: string,
514+
encodeFormAction: EncodeFormActionCallback,
515+
): ReactCustomFormAction {
516+
const reference = knownServerReferences.get(proxy);
517+
if (!reference) {
518+
throw new Error(
519+
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
520+
'This is a bug in React.',
521+
);
522+
}
523+
let boundPromise: Promise<Array<any>> = (reference.bound: any);
524+
if (boundPromise === null) {
525+
boundPromise = Promise.resolve([]);
526+
}
527+
return encodeFormAction(reference.id, boundPromise);
528+
}
529+
506530
function isSignatureEqual(
507531
this: any => Promise<any>,
508532
referenceId: ServerReferenceId,
@@ -569,13 +593,27 @@ function isSignatureEqual(
569593
export function registerServerReference(
570594
proxy: any,
571595
reference: {id: ServerReferenceId, bound: null | Thenable<Array<any>>},
596+
encodeFormAction: void | EncodeFormActionCallback,
572597
) {
573598
// Expose encoder for use by SSR, as well as a special bind that can be used to
574599
// keep server capabilities.
575600
if (usedWithSSR) {
576601
// Only expose this in builds that would actually use it. Not needed on the client.
602+
const $$FORM_ACTION =
603+
encodeFormAction === undefined
604+
? defaultEncodeFormAction
605+
: function (
606+
this: any => Promise<any>,
607+
identifierPrefix: string,
608+
): ReactCustomFormAction {
609+
return customEncodeFormAction(
610+
this,
611+
identifierPrefix,
612+
encodeFormAction,
613+
);
614+
};
577615
Object.defineProperties((proxy: any), {
578-
$$FORM_ACTION: {value: encodeFormAction},
616+
$$FORM_ACTION: {value: $$FORM_ACTION},
579617
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
580618
bind: {value: bind},
581619
});
@@ -587,7 +625,7 @@ export function registerServerReference(
587625
const FunctionBind = Function.prototype.bind;
588626
// $FlowFixMe[method-unbinding]
589627
const ArraySlice = Array.prototype.slice;
590-
function bind(this: Function) {
628+
function bind(this: Function): Function {
591629
// $FlowFixMe[unsupported-syntax]
592630
const newFn = FunctionBind.apply(this, arguments);
593631
const reference = knownServerReferences.get(this);
@@ -601,20 +639,31 @@ function bind(this: Function) {
601639
} else {
602640
boundPromise = Promise.resolve(args);
603641
}
604-
registerServerReference(newFn, {id: reference.id, bound: boundPromise});
642+
// Expose encoder for use by SSR, as well as a special bind that can be used to
643+
// keep server capabilities.
644+
if (usedWithSSR) {
645+
// Only expose this in builds that would actually use it. Not needed on the client.
646+
Object.defineProperties((newFn: any), {
647+
$$FORM_ACTION: {value: this.$$FORM_ACTION},
648+
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
649+
bind: {value: bind},
650+
});
651+
}
652+
knownServerReferences.set(newFn, {id: reference.id, bound: boundPromise});
605653
}
606654
return newFn;
607655
}
608656

609657
export function createServerReference<A: Iterable<any>, T>(
610658
id: ServerReferenceId,
611659
callServer: CallServerCallback,
660+
encodeFormAction?: EncodeFormActionCallback,
612661
): (...A) => Promise<T> {
613662
const proxy = function (): Promise<T> {
614663
// $FlowFixMe[method-unbinding]
615664
const args = Array.prototype.slice.call(arguments);
616665
return callServer(id, args);
617666
};
618-
registerServerReference(proxy, {id, bound: null});
667+
registerServerReference(proxy, {id, bound: null}, encodeFormAction);
619668
return proxy;
620669
}

packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function createResponseFromOptions(options: void | Options) {
3838
options && options.moduleBaseURL ? options.moduleBaseURL : '',
3939
null,
4040
options && options.callServer ? options.callServer : undefined,
41+
undefined, // encodeFormAction
4142
undefined, // nonce
4243
);
4344
}

packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes.js';
10+
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
1111

1212
import type {Response} from 'react-client/src/ReactFlightClient';
1313

@@ -38,8 +38,14 @@ export function createServerReference<A: Iterable<any>, T>(
3838
return createServerReferenceImpl(id, noServerCall);
3939
}
4040

41+
type EncodeFormActionCallback = <A>(
42+
id: any,
43+
args: Promise<A>,
44+
) => ReactCustomFormAction;
45+
4146
export type Options = {
4247
nonce?: string,
48+
encodeFormAction?: EncodeFormActionCallback,
4349
};
4450

4551
function createFromNodeStream<T>(
@@ -52,6 +58,7 @@ function createFromNodeStream<T>(
5258
moduleRootPath,
5359
moduleBaseURL,
5460
noServerCall,
61+
options ? options.encodeFormAction : undefined,
5562
options && typeof options.nonce === 'string' ? options.nonce : undefined,
5663
);
5764
stream.on('data', chunk => {

packages/react-server-dom-turbopack/src/ReactFlightDOMClientBrowser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function createResponseFromOptions(options: void | Options) {
3737
null,
3838
null,
3939
options && options.callServer ? options.callServer : undefined,
40+
undefined, // encodeFormAction
4041
undefined, // nonce
4142
);
4243
}

packages/react-server-dom-turbopack/src/ReactFlightDOMClientEdge.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes.js';
10+
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
1111

1212
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
1313

@@ -51,16 +51,23 @@ export function createServerReference<A: Iterable<any>, T>(
5151
return createServerReferenceImpl(id, noServerCall);
5252
}
5353

54+
type EncodeFormActionCallback = <A>(
55+
id: any,
56+
args: Promise<A>,
57+
) => ReactCustomFormAction;
58+
5459
export type Options = {
5560
ssrManifest: SSRManifest,
5661
nonce?: string,
62+
encodeFormAction?: EncodeFormActionCallback,
5763
};
5864

5965
function createResponseFromOptions(options: Options) {
6066
return createResponse(
6167
options.ssrManifest.moduleMap,
6268
options.ssrManifest.moduleLoading,
6369
noServerCall,
70+
options.encodeFormAction,
6471
typeof options.nonce === 'string' ? options.nonce : undefined,
6572
);
6673
}

packages/react-server-dom-turbopack/src/ReactFlightDOMClientNode.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes.js';
10+
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
1111

1212
import type {Response} from 'react-client/src/ReactFlightClient';
1313

@@ -40,9 +40,6 @@ function noServerCall() {
4040
'to pass data to Client Components instead.',
4141
);
4242
}
43-
export type Options = {
44-
nonce?: string,
45-
};
4643

4744
export function createServerReference<A: Iterable<any>, T>(
4845
id: any,
@@ -51,6 +48,16 @@ export function createServerReference<A: Iterable<any>, T>(
5148
return createServerReferenceImpl(id, noServerCall);
5249
}
5350

51+
type EncodeFormActionCallback = <A>(
52+
id: any,
53+
args: Promise<A>,
54+
) => ReactCustomFormAction;
55+
56+
export type Options = {
57+
nonce?: string,
58+
encodeFormAction?: EncodeFormActionCallback,
59+
};
60+
5461
function createFromNodeStream<T>(
5562
stream: Readable,
5663
ssrManifest: SSRManifest,
@@ -60,6 +67,7 @@ function createFromNodeStream<T>(
6067
ssrManifest.moduleMap,
6168
ssrManifest.moduleLoading,
6269
noServerCall,
70+
options ? options.encodeFormAction : undefined,
6371
options && typeof options.nonce === 'string' ? options.nonce : undefined,
6472
);
6573
stream.on('data', chunk => {

packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function createResponseFromOptions(options: void | Options) {
3737
null,
3838
null,
3939
options && options.callServer ? options.callServer : undefined,
40+
undefined, // encodeFormAction
4041
undefined, // nonce
4142
);
4243
}

packages/react-server-dom-webpack/src/ReactFlightDOMClientEdge.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes.js';
10+
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
1111

1212
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
1313

@@ -51,16 +51,23 @@ export function createServerReference<A: Iterable<any>, T>(
5151
return createServerReferenceImpl(id, noServerCall);
5252
}
5353

54+
type EncodeFormActionCallback = <A>(
55+
id: any,
56+
args: Promise<A>,
57+
) => ReactCustomFormAction;
58+
5459
export type Options = {
5560
ssrManifest: SSRManifest,
5661
nonce?: string,
62+
encodeFormAction?: EncodeFormActionCallback,
5763
};
5864

5965
function createResponseFromOptions(options: Options) {
6066
return createResponse(
6167
options.ssrManifest.moduleMap,
6268
options.ssrManifest.moduleLoading,
6369
noServerCall,
70+
options.encodeFormAction,
6471
typeof options.nonce === 'string' ? options.nonce : undefined,
6572
);
6673
}

packages/react-server-dom-webpack/src/ReactFlightDOMClientNode.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactTypes.js';
10+
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
1111

1212
import type {Response} from 'react-client/src/ReactFlightClient';
1313

@@ -48,8 +48,14 @@ export function createServerReference<A: Iterable<any>, T>(
4848
return createServerReferenceImpl(id, noServerCall);
4949
}
5050

51+
type EncodeFormActionCallback = <A>(
52+
id: any,
53+
args: Promise<A>,
54+
) => ReactCustomFormAction;
55+
5156
export type Options = {
5257
nonce?: string,
58+
encodeFormAction?: EncodeFormActionCallback,
5359
};
5460

5561
function createFromNodeStream<T>(
@@ -61,6 +67,7 @@ function createFromNodeStream<T>(
6167
ssrManifest.moduleMap,
6268
ssrManifest.moduleLoading,
6369
noServerCall,
70+
options ? options.encodeFormAction : undefined,
6471
options && typeof options.nonce === 'string' ? options.nonce : undefined,
6572
);
6673
stream.on('data', chunk => {

0 commit comments

Comments
 (0)