mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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 https://github.com/vercel/next.js/issues/50087
This commit is contained in:
@@ -1068,11 +1068,21 @@ export function canHydrateInstance(
|
||||
if (
|
||||
enableFormActions &&
|
||||
type === 'input' &&
|
||||
(element: any).type === 'hidden' &&
|
||||
anyProps.type !== 'hidden'
|
||||
(element: any).type === 'hidden'
|
||||
) {
|
||||
// Skip past hidden inputs unless that's what we're looking for. This allows us
|
||||
// embed extra form data in the original form.
|
||||
if (__DEV__) {
|
||||
checkAttributeStringCoercion(anyProps.name, 'name');
|
||||
}
|
||||
const name = anyProps.name == null ? null : '' + anyProps.name;
|
||||
if (
|
||||
anyProps.type !== 'hidden' ||
|
||||
element.getAttribute('name') !== name
|
||||
) {
|
||||
// Skip past hidden inputs unless that's what we're looking for. This allows us
|
||||
// embed extra form data in the original form.
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
|
||||
+53
-1
@@ -524,6 +524,7 @@ describe('ReactDOMFizzForm', () => {
|
||||
|
||||
// @gate enableFormActions
|
||||
it('can provide a custom action on buttons the server for actions', async () => {
|
||||
const hiddenRef = React.createRef();
|
||||
const inputRef = React.createRef();
|
||||
const buttonRef = React.createRef();
|
||||
let foo;
|
||||
@@ -546,7 +547,7 @@ describe('ReactDOMFizzForm', () => {
|
||||
function App() {
|
||||
return (
|
||||
<form>
|
||||
<input type="hidden" name="foo" value="bar" />
|
||||
<input type="hidden" name="foo" value="bar" ref={hiddenRef} />
|
||||
<input
|
||||
type="submit"
|
||||
formAction={action}
|
||||
@@ -588,6 +589,8 @@ describe('ReactDOMFizzForm', () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
});
|
||||
|
||||
expect(hiddenRef.current.name).toBe('foo');
|
||||
|
||||
submit(inputRef.current);
|
||||
|
||||
expect(foo).toBe('bar');
|
||||
@@ -598,4 +601,53 @@ describe('ReactDOMFizzForm', () => {
|
||||
|
||||
expect(foo).toBe('bar');
|
||||
});
|
||||
|
||||
// @gate enableFormActions
|
||||
it('can hydrate hidden fields in the beginning of a form', async () => {
|
||||
const hiddenRef = React.createRef();
|
||||
|
||||
let invoked = false;
|
||||
function action(formData) {
|
||||
invoked = true;
|
||||
}
|
||||
action.$$FORM_ACTION = function (identifierPrefix) {
|
||||
const extraFields = new FormData();
|
||||
extraFields.append(identifierPrefix + 'hello', 'world');
|
||||
return {
|
||||
action: '',
|
||||
name: identifierPrefix,
|
||||
method: 'POST',
|
||||
encType: 'multipart/form-data',
|
||||
data: extraFields,
|
||||
};
|
||||
};
|
||||
function App() {
|
||||
return (
|
||||
<form action={action}>
|
||||
<input type="hidden" name="bar" defaultValue="baz" ref={hiddenRef} />
|
||||
<input type="text" name="foo" defaultValue="bar" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
const barField = container.querySelector('[name=bar]');
|
||||
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
});
|
||||
|
||||
expect(hiddenRef.current).toBe(barField);
|
||||
|
||||
expect(hiddenRef.current.name).toBe('bar');
|
||||
expect(hiddenRef.current.value).toBe('baz');
|
||||
|
||||
expect(container.querySelectorAll('[name=bar]').length).toBe(1);
|
||||
|
||||
submit(hiddenRef.current.form);
|
||||
|
||||
expect(invoked).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user