Skip to content

Commit 04e4daa

Browse files
committed
Allow hidden inputs as extra nodes that we don't match
Really this could be limited to just buttons but I just made it a general thing.
1 parent dfded30 commit 04e4daa

File tree

2 files changed

+59
-3
lines changed

2 files changed

+59
-3
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,20 +1047,39 @@ export function canHydrateInstance(
10471047
): null | Instance {
10481048
while (instance.nodeType === ELEMENT_NODE) {
10491049
const element: Element = (instance: any);
1050+
const anyProps = (props: any);
10501051
if (element.nodeName.toLowerCase() !== type.toLowerCase()) {
10511052
if (!inRootOrSingleton || !enableHostSingletons) {
10521053
// Usually we error for mismatched tags.
1053-
return null;
1054+
if (
1055+
enableFormActions &&
1056+
element.nodeName === 'INPUT' &&
1057+
(element: any).type === 'hidden'
1058+
) {
1059+
// If we have extra hidden inputs, we don't mismatch. This allows us to embed
1060+
// extra form data in the original form.
1061+
} else {
1062+
return null;
1063+
}
10541064
}
10551065
// In root or singleton parents we skip past mismatched instances.
10561066
} else if (!inRootOrSingleton || !enableHostSingletons) {
10571067
// Match
1058-
return element;
1068+
if (
1069+
enableFormActions &&
1070+
type === 'input' &&
1071+
(element: any).type === 'hidden' &&
1072+
anyProps.type !== 'hidden'
1073+
) {
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.
1076+
} else {
1077+
return element;
1078+
}
10591079
} else if (isMarkedHoistable(element)) {
10601080
// We've already claimed this as a hoistable which isn't hydrated this way so we skip past it.
10611081
} else {
10621082
// We have an Element with the right type.
1063-
const anyProps = (props: any);
10641083

10651084
// We are going to try to exclude it if we can definitely identify it as a hoisted Node or if
10661085
// we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,4 +695,41 @@ describe('ReactDOMServerHydration', () => {
695695
);
696696
}
697697
});
698+
699+
// @gate enableFormActions
700+
it('allows rendering extra hidden inputs in a form', async () => {
701+
const element = document.createElement('div');
702+
element.innerHTML =
703+
'<form>' +
704+
'<input type="hidden" /><input type="hidden" name="a" value="A" />' +
705+
'<input type="hidden" /><input type="submit" name="b" value="B" />' +
706+
'<input type="hidden" /><button name="c" value="C" />' +
707+
'<input type="hidden" />' +
708+
'</form>';
709+
const form = element.firstChild;
710+
const ref = React.createRef();
711+
const a = React.createRef();
712+
const b = React.createRef();
713+
const c = React.createRef();
714+
await act(async () => {
715+
ReactDOMClient.hydrateRoot(
716+
element,
717+
<form ref={ref}>
718+
<input type="hidden" name="a" value="A" ref={a} />
719+
<input type="submit" name="b" value="B" ref={b} />
720+
<button name="c" value="C" ref={c} />
721+
</form>,
722+
);
723+
});
724+
725+
// The content should not have been client rendered.
726+
expect(ref.current).toBe(form);
727+
728+
expect(a.current.name).toBe('a');
729+
expect(a.current.value).toBe('A');
730+
expect(b.current.name).toBe('b');
731+
expect(b.current.value).toBe('B');
732+
expect(c.current.name).toBe('c');
733+
expect(c.current.value).toBe('C');
734+
});
698735
});

0 commit comments

Comments
 (0)