Skip to content

Commit 9832a1b

Browse files
ellsclytngaearon
authored andcommitted
Avoid setting empty value on reset & submit inputs (#12780)
* Avoid setting empty value on reset & submit inputs * Update ReactDOMFiberInput.js * More test coverage
1 parent 8862172 commit 9832a1b

File tree

2 files changed

+148
-5
lines changed

2 files changed

+148
-5
lines changed

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

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,15 +752,142 @@ describe('ReactDOMInput', () => {
752752

753753
it('should not set a value for submit buttons unnecessarily', () => {
754754
const stub = <input type="submit" />;
755-
const node = ReactDOM.render(stub, container);
755+
ReactDOM.render(stub, container);
756+
const node = container.firstChild;
756757

757758
// The value shouldn't be '', or else the button will have no text; it
758759
// should have the default "Submit" or "Submit Query" label. Most browsers
759760
// report this as not having a `value` attribute at all; IE reports it as
760761
// the actual label that the user sees.
761-
expect(
762-
!node.hasAttribute('value') || node.getAttribute('value').length > 0,
763-
).toBe(true);
762+
expect(node.hasAttribute('value')).toBe(false);
763+
});
764+
765+
it('should remove the value attribute on submit inputs when value is updated to undefined', () => {
766+
const stub = <input type="submit" value="foo" onChange={emptyFunction} />;
767+
ReactDOM.render(stub, container);
768+
769+
// Not really relevant to this particular test, but changing to undefined
770+
// should nonetheless trigger a warning
771+
expect(() =>
772+
ReactDOM.render(
773+
<input type="submit" value={undefined} onChange={emptyFunction} />,
774+
container,
775+
),
776+
).toWarnDev(
777+
'A component is changing a controlled input of type ' +
778+
'submit to be uncontrolled.',
779+
);
780+
781+
const node = container.firstChild;
782+
expect(node.getAttribute('value')).toBe(null);
783+
});
784+
785+
it('should remove the value attribute on reset inputs when value is updated to undefined', () => {
786+
const stub = <input type="reset" value="foo" onChange={emptyFunction} />;
787+
ReactDOM.render(stub, container);
788+
789+
// Not really relevant to this particular test, but changing to undefined
790+
// should nonetheless trigger a warning
791+
expect(() =>
792+
ReactDOM.render(
793+
<input type="reset" value={undefined} onChange={emptyFunction} />,
794+
container,
795+
),
796+
).toWarnDev(
797+
'A component is changing a controlled input of type ' +
798+
'reset to be uncontrolled.',
799+
);
800+
801+
const node = container.firstChild;
802+
expect(node.getAttribute('value')).toBe(null);
803+
});
804+
805+
it('should set a value on a submit input', () => {
806+
let stub = <input type="submit" value="banana" />;
807+
ReactDOM.render(stub, container);
808+
const node = container.firstChild;
809+
810+
expect(node.getAttribute('value')).toBe('banana');
811+
});
812+
813+
it('should not set an undefined value on a submit input', () => {
814+
let stub = <input type="submit" value={undefined} />;
815+
ReactDOM.render(stub, container);
816+
const node = container.firstChild;
817+
818+
// Note: it shouldn't be an empty string
819+
// because that would erase the "submit" label.
820+
expect(node.getAttribute('value')).toBe(null);
821+
822+
ReactDOM.render(stub, container);
823+
expect(node.getAttribute('value')).toBe(null);
824+
});
825+
826+
it('should not set an undefined value on a reset input', () => {
827+
let stub = <input type="reset" value={undefined} />;
828+
ReactDOM.render(stub, container);
829+
const node = container.firstChild;
830+
831+
// Note: it shouldn't be an empty string
832+
// because that would erase the "reset" label.
833+
expect(node.getAttribute('value')).toBe(null);
834+
835+
ReactDOM.render(stub, container);
836+
expect(node.getAttribute('value')).toBe(null);
837+
});
838+
839+
it('should not set a null value on a submit input', () => {
840+
let stub = <input type="submit" value={null} />;
841+
expect(() => {
842+
ReactDOM.render(stub, container);
843+
}).toWarnDev('`value` prop on `input` should not be null');
844+
const node = container.firstChild;
845+
846+
// Note: it shouldn't be an empty string
847+
// because that would erase the "submit" label.
848+
expect(node.getAttribute('value')).toBe(null);
849+
850+
ReactDOM.render(stub, container);
851+
expect(node.getAttribute('value')).toBe(null);
852+
});
853+
854+
it('should not set a null value on a reset input', () => {
855+
let stub = <input type="reset" value={null} />;
856+
expect(() => {
857+
ReactDOM.render(stub, container);
858+
}).toWarnDev('`value` prop on `input` should not be null');
859+
const node = container.firstChild;
860+
861+
// Note: it shouldn't be an empty string
862+
// because that would erase the "reset" label.
863+
expect(node.getAttribute('value')).toBe(null);
864+
865+
ReactDOM.render(stub, container);
866+
expect(node.getAttribute('value')).toBe(null);
867+
});
868+
869+
it('should set a value on a reset input', () => {
870+
let stub = <input type="reset" value="banana" />;
871+
ReactDOM.render(stub, container);
872+
const node = container.firstChild;
873+
874+
expect(node.getAttribute('value')).toBe('banana');
875+
});
876+
877+
it('should set an empty string value on a submit input', () => {
878+
let stub = <input type="submit" value="" />;
879+
ReactDOM.render(stub, container);
880+
const node = container.firstChild;
881+
882+
expect(node.getAttribute('value')).toBe('');
883+
});
884+
885+
it('should set an empty string value on a reset input', () => {
886+
let stub = <input type="reset" value="" />;
887+
ReactDOM.render(stub, container);
888+
const node = container.firstChild;
889+
890+
expect(node.getAttribute('value')).toBe('');
764891
});
765892

766893
it('should control radio buttons', () => {

packages/react-dom/src/client/ReactDOMFiberInput.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ export function updateWrapper(element: Element, props: Object) {
172172
updateChecked(element, props);
173173

174174
const value = getToStringValue(props.value);
175+
const type = props.type;
175176

176177
if (value != null) {
177-
if (props.type === 'number') {
178+
if (type === 'number') {
178179
if (
179180
(value === 0 && node.value === '') ||
180181
// We explicitly want to coerce to number here if possible.
@@ -186,6 +187,11 @@ export function updateWrapper(element: Element, props: Object) {
186187
} else if (node.value !== toString(value)) {
187188
node.value = toString(value);
188189
}
190+
} else if (type === 'submit' || type === 'reset') {
191+
// Submit/reset inputs need the attribute removed completely to avoid
192+
// blank-text buttons.
193+
node.removeAttribute('value');
194+
return;
189195
}
190196

191197
if (props.hasOwnProperty('value')) {
@@ -207,6 +213,16 @@ export function postMountWrapper(
207213
const node = ((element: any): InputWithWrapperState);
208214

209215
if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {
216+
// Avoid setting value attribute on submit/reset inputs as it overrides the
217+
// default value provided by the browser. See: #12872
218+
const type = props.type;
219+
if (
220+
(type === 'submit' || type === 'reset') &&
221+
(props.value === undefined || props.value === null)
222+
) {
223+
return;
224+
}
225+
210226
const initialValue = toString(node._wrapperState.initialValue);
211227
const currentValue = node.value;
212228

0 commit comments

Comments
 (0)