From 976da3ea8544dc178f5eb0ebf29e729c9af68f5f Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 18 Feb 2024 21:31:24 -0500 Subject: [PATCH] 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. --- .../src/ReactFlightReplyClient.js | 11 +++++++ .../src/ReactFlightWebpackReferences.js | 8 +++++ .../src/__tests__/ReactFlightDOMEdge-test.js | 29 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 650980cc2c1e3..6bae3d1a14de5 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -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) { diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index b174953a72c4f..711b1af6c2c2f 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -65,6 +65,14 @@ function bind(this: ServerReference): 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}, diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js index 4b64875c3f89f..cce874d8148db 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js @@ -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}, + ); + }); });