diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js index 65552a23f263a..7e1955b753107 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js @@ -388,4 +388,45 @@ describe('ReactFlightDOMForm', () => { expect(form.target).toBe('/permalink'); }); + + // @gate enableFormActions + // @gate enableAsyncActions + it('useFormState `permalink` is coerced to string', async () => { + const serverAction = serverExports(function action(prevState) { + return {state: prevState.count + 1}; + }); + + class Permalink { + toString() { + return '/permalink'; + } + } + + const permalink = new Permalink(); + + const initialState = {count: 1}; + function Client({action}) { + const [state, dispatch] = useFormState(action, initialState, permalink); + return ( +
+ Count: {state.count} +
+ ); + } + const ClientRef = await clientExports(Client); + + const rscStream = ReactServerDOMServer.renderToReadableStream( + , + webpackMap, + ); + const response = ReactServerDOMClient.createFromReadableStream(rscStream); + const ssrStream = await ReactDOMServer.renderToReadableStream(response); + await readIntoContainer(ssrStream); + + const form = container.firstChild; + const span = container.getElementsByTagName('span')[0]; + expect(span.textContent).toBe('Count: 1'); + + expect(form.target).toBe('/permalink'); + }); }); diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 9a031a1f3409d..377c21a9fe637 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -41,6 +41,7 @@ import { REACT_CONTEXT_TYPE, REACT_MEMO_CACHE_SENTINEL, } from 'shared/ReactSymbols'; +import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; type BasicStateAction = (S => S) | S; type Dispatch = A => void; @@ -575,8 +576,11 @@ function useFormState( // $FlowIgnore[prop-missing] const metadata: ReactCustomFormAction = boundAction.$$FORM_ACTION(prefix); // Override the target URL - if (typeof permalink === 'string') { - metadata.target = permalink; + if (permalink !== undefined) { + if (__DEV__) { + checkAttributeStringCoercion(permalink, 'target'); + } + metadata.target = permalink + ''; } return metadata; };