Skip to content

Commit a1f9758

Browse files
authored
Compare name when hydrating hidden fields to filter out extra form action fields (#26846)
This solves an issue where if you inject a hidden field in the beginning of the form, we might mistakenly hydrate the injected one that was part of an action. I'm not too happy about how specific this becomes. It's similar to Float but in general we don't do this deep comparison. See vercel/next.js#50087
1 parent 0210f0b commit a1f9758

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,11 +1068,21 @@ export function canHydrateInstance(
10681068
if (
10691069
enableFormActions &&
10701070
type === 'input' &&
1071-
(element: any).type === 'hidden' &&
1072-
anyProps.type !== 'hidden'
1071+
(element: any).type === 'hidden'
10731072
) {
1074-
// Skip past hidden inputs unless that's what we're looking for. This allows us
1075-
// embed extra form data in the original form.
1073+
if (__DEV__) {
1074+
checkAttributeStringCoercion(anyProps.name, 'name');
1075+
}
1076+
const name = anyProps.name == null ? null : '' + anyProps.name;
1077+
if (
1078+
anyProps.type !== 'hidden' ||
1079+
element.getAttribute('name') !== name
1080+
) {
1081+
// Skip past hidden inputs unless that's what we're looking for. This allows us
1082+
// embed extra form data in the original form.
1083+
} else {
1084+
return element;
1085+
}
10761086
} else {
10771087
return element;
10781088
}

packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ describe('ReactDOMFizzForm', () => {
524524

525525
// @gate enableFormActions
526526
it('can provide a custom action on buttons the server for actions', async () => {
527+
const hiddenRef = React.createRef();
527528
const inputRef = React.createRef();
528529
const buttonRef = React.createRef();
529530
let foo;
@@ -546,7 +547,7 @@ describe('ReactDOMFizzForm', () => {
546547
function App() {
547548
return (
548549
<form>
549-
<input type="hidden" name="foo" value="bar" />
550+
<input type="hidden" name="foo" value="bar" ref={hiddenRef} />
550551
<input
551552
type="submit"
552553
formAction={action}
@@ -588,6 +589,8 @@ describe('ReactDOMFizzForm', () => {
588589
ReactDOMClient.hydrateRoot(container, <App />);
589590
});
590591

592+
expect(hiddenRef.current.name).toBe('foo');
593+
591594
submit(inputRef.current);
592595

593596
expect(foo).toBe('bar');
@@ -598,4 +601,53 @@ describe('ReactDOMFizzForm', () => {
598601

599602
expect(foo).toBe('bar');
600603
});
604+
605+
// @gate enableFormActions
606+
it('can hydrate hidden fields in the beginning of a form', async () => {
607+
const hiddenRef = React.createRef();
608+
609+
let invoked = false;
610+
function action(formData) {
611+
invoked = true;
612+
}
613+
action.$$FORM_ACTION = function (identifierPrefix) {
614+
const extraFields = new FormData();
615+
extraFields.append(identifierPrefix + 'hello', 'world');
616+
return {
617+
action: '',
618+
name: identifierPrefix,
619+
method: 'POST',
620+
encType: 'multipart/form-data',
621+
data: extraFields,
622+
};
623+
};
624+
function App() {
625+
return (
626+
<form action={action}>
627+
<input type="hidden" name="bar" defaultValue="baz" ref={hiddenRef} />
628+
<input type="text" name="foo" defaultValue="bar" />
629+
</form>
630+
);
631+
}
632+
633+
const stream = await ReactDOMServer.renderToReadableStream(<App />);
634+
await readIntoContainer(stream);
635+
636+
const barField = container.querySelector('[name=bar]');
637+
638+
await act(async () => {
639+
ReactDOMClient.hydrateRoot(container, <App />);
640+
});
641+
642+
expect(hiddenRef.current).toBe(barField);
643+
644+
expect(hiddenRef.current.name).toBe('bar');
645+
expect(hiddenRef.current.value).toBe('baz');
646+
647+
expect(container.querySelectorAll('[name=bar]').length).toBe(1);
648+
649+
submit(hiddenRef.current.form);
650+
651+
expect(invoked).toBe(true);
652+
});
601653
});

0 commit comments

Comments
 (0)