Repository: facebook/react
Pull request: [Flight] Warn if this argument is passed to .bind of a Server Reference

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.

Changed files:
filename: packages/react-client/src/ReactFlightReplyClient.js
patch: @@ -635,6 +635,17 @@ function bind(this: Function): Function {
   const newFn = FunctionBind.apply(this, arguments);
   const reference = knownServerReferences.get(this);
   if (reference) {
+    if (__DEV__) {
+      const thisBind = arguments[0];
+      if (thisBind != null) {
+        // This doesn't warn in browser environments since it's not instrumented outside
+        // usedWithSSR. This makes this an SSR only warning which we don't generally do.
+        // TODO: Consider a DEV only instrumentation in the browser.
+        console.error(
+          'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
+        );
+      }
+    }
     const args = ArraySlice.call(arguments, 1);
     let boundPromise = null;
     if (reference.bound !== null) {

filename: packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
patch: @@ -65,6 +65,14 @@ function bind(this: ServerReference<any>): any {
   // $FlowFixMe[unsupported-syntax]
   const newFn = FunctionBind.apply(this, arguments);
   if (this.$$typeof === SERVER_REFERENCE_TAG) {
+    if (__DEV__) {
+      const thisBind = arguments[0];
+      if (thisBind != null) {
+        console.error(
+          'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
+        );
+      }
+    }
     const args = ArraySlice.call(arguments, 1);
     return Object.defineProperties((newFn: any), {
       $$typeof: {value: SERVER_REFERENCE_TAG},

filename: packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
patch: @@ -19,6 +19,7 @@ global.TextDecoder = require('util').TextDecoder;
 // TODO: we can replace this with FlightServer.act().
 global.setTimeout = cb => cb();
 
+let serverExports;
 let clientExports;
 let webpackMap;
 let webpackModules;
@@ -41,6 +42,7 @@ describe('ReactFlightDOMEdge', () => {
 
     const WebpackMock = require('./utils/WebpackMock');
 
+    serverExports = WebpackMock.serverExports;
     clientExports = WebpackMock.clientExports;
     webpackMap = WebpackMock.webpackMap;
     webpackModules = WebpackMock.webpackModules;
@@ -323,4 +325,31 @@ describe('ReactFlightDOMEdge', () => {
     });
     expect(result).toEqual(buffers);
   });
+
+  it('warns if passing a this argument to bind() of a server reference', async () => {
+    const ServerModule = serverExports({
+      greet: function () {},
+    });
+
+    const ServerModuleImportedOnClient = {
+      greet: ReactServerDOMClient.createServerReference(
+        ServerModule.greet.$$id,
+        async function (ref, args) {},
+      ),
+    };
+
+    expect(() => {
+      ServerModule.greet.bind({}, 'hi');
+    }).toErrorDev(
+      'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
+      {withoutStack: true},
+    );
+
+    expect(() => {
+      ServerModuleImportedOnClient.greet.bind({}, 'hi');
+    }).toErrorDev(
+      'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
+      {withoutStack: true},
+    );
+  });
 });