[Flight] Warn if this argument is passed to .bind of a Server Reference (#28380)
This won't ever be serialized and is likely just a mistake. This should be covered by the "use server" compiler since it ensures that something that accepts a "this" won't be allowed to compile and if it doesn't accept it, TypeScript should ideally forbid it to be passed. So maybe this is unnecessary.
Sebastian Markbåge committed
Feb 19, 2024 at 11:50 UTC
65a0e2b25ec4642f83c32d7599b5252c3955a60b
3 files changed
+48
packages/react-client/src/ReactFlightReplyClient.js
+11
@@ -635,6 +635,17 @@ function bind(this: Function): Function {
635
const newFn = FunctionBind.apply(this, arguments);
636
const reference = knownServerReferences.get(this);
637
if (reference) {
638
+ if (__DEV__) {
639
+ const thisBind = arguments[0];
640
+ if (thisBind != null) {
641
+ // This doesn't warn in browser environments since it's not instrumented outside
642
+ // usedWithSSR. This makes this an SSR only warning which we don't generally do.
643
+ // TODO: Consider a DEV only instrumentation in the browser.
644
+ console.error(
645
+ 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
646
+ );
647
+ }
648
+ }
649
const args = ArraySlice.call(arguments, 1);
650
let boundPromise = null;
651
if (reference.bound !== null) {
packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
+8
@@ -65,6 +65,14 @@ function bind(this: ServerReference<any>): any {
65
// $FlowFixMe[unsupported-syntax]
66
const newFn = FunctionBind.apply(this, arguments);
67
if (this.$$typeof === SERVER_REFERENCE_TAG) {
68
+ if (__DEV__) {
69
+ const thisBind = arguments[0];
70
+ if (thisBind != null) {
71
+ console.error(
72
+ 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
73
+ );
74
+ }
75
+ }
76
const args = ArraySlice.call(arguments, 1);
77
return Object.defineProperties((newFn: any), {
78
$$typeof: {value: SERVER_REFERENCE_TAG},
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+29
@@ -19,6 +19,7 @@ global.TextDecoder = require('util').TextDecoder;
19
// TODO: we can replace this with FlightServer.act().
20
global.setTimeout = cb => cb();
21
22
+let serverExports;
23
let clientExports;
24
let webpackMap;
25
let webpackModules;
@@ -41,6 +42,7 @@ describe('ReactFlightDOMEdge', () => {
42
43
const WebpackMock = require('./utils/WebpackMock');
44
45
+ serverExports = WebpackMock.serverExports;
46
clientExports = WebpackMock.clientExports;
47
webpackMap = WebpackMock.webpackMap;
48
webpackModules = WebpackMock.webpackModules;
@@ -323,4 +325,31 @@ describe('ReactFlightDOMEdge', () => {
325
});
326
expect(result).toEqual(buffers);
327
});
328
+
329
+ it('warns if passing a this argument to bind() of a server reference', async () => {
330
+ const ServerModule = serverExports({
331
+ greet: function () {},
332
+ });
333
+
334
+ const ServerModuleImportedOnClient = {
335
+ greet: ReactServerDOMClient.createServerReference(
336
+ ServerModule.greet.$$id,
337
+ async function (ref, args) {},
338
+ ),
339
+ };
340
+
341
+ expect(() => {
342
+ ServerModule.greet.bind({}, 'hi');
343
+ }).toErrorDev(
344
+ 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
345
+ {withoutStack: true},
346
+ );
347
+
348
+ expect(() => {
349
+ ServerModuleImportedOnClient.greet.bind({}, 'hi');
350
+ }).toErrorDev(
351
+ 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
352
+ {withoutStack: true},
353
+ );
354
+ });
355
});