@samitouri / QOS-React / commits / 65a0e2b25e

[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 });