Skip to content

Commit 3216b92

Browse files
committed
Coerce permalink to string
1 parent c538193 commit 3216b92

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,4 +388,45 @@ describe('ReactFlightDOMForm', () => {
388388

389389
expect(form.target).toBe('/permalink');
390390
});
391+
392+
// @gate enableFormActions
393+
// @gate enableAsyncActions
394+
it('useFormState `permalink` is coerced to string', async () => {
395+
const serverAction = serverExports(function action(prevState) {
396+
return {state: prevState.count + 1};
397+
});
398+
399+
class Permalink {
400+
toString() {
401+
return '/permalink';
402+
}
403+
}
404+
405+
const permalink = new Permalink();
406+
407+
const initialState = {count: 1};
408+
function Client({action}) {
409+
const [state, dispatch] = useFormState(action, initialState, permalink);
410+
return (
411+
<form action={dispatch}>
412+
<span>Count: {state.count}</span>
413+
</form>
414+
);
415+
}
416+
const ClientRef = await clientExports(Client);
417+
418+
const rscStream = ReactServerDOMServer.renderToReadableStream(
419+
<ClientRef action={serverAction} />,
420+
webpackMap,
421+
);
422+
const response = ReactServerDOMClient.createFromReadableStream(rscStream);
423+
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
424+
await readIntoContainer(ssrStream);
425+
426+
const form = container.firstChild;
427+
const span = container.getElementsByTagName('span')[0];
428+
expect(span.textContent).toBe('Count: 1');
429+
430+
expect(form.target).toBe('/permalink');
431+
});
391432
});

packages/react-server/src/ReactFizzHooks.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
REACT_CONTEXT_TYPE,
4242
REACT_MEMO_CACHE_SENTINEL,
4343
} from 'shared/ReactSymbols';
44+
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
4445

4546
type BasicStateAction<S> = (S => S) | S;
4647
type Dispatch<A> = A => void;
@@ -575,8 +576,11 @@ function useFormState<S, P>(
575576
// $FlowIgnore[prop-missing]
576577
const metadata: ReactCustomFormAction = boundAction.$$FORM_ACTION(prefix);
577578
// Override the target URL
578-
if (typeof permalink === 'string') {
579-
metadata.target = permalink;
579+
if (permalink !== undefined) {
580+
if (__DEV__) {
581+
checkAttributeStringCoercion(permalink, 'target');
582+
}
583+
metadata.target = permalink + '';
580584
}
581585
return metadata;
582586
};

0 commit comments

Comments
 (0)