Skip to content

Commit 76eab7c

Browse files
fix(attributes-to-props): don't convert non-uncontrolled component props
Fixes #839
1 parent 8d0f7ad commit 76eab7c

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

lib/attributes-to-props.js

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
var reactProperty = require('react-property');
22
var utilities = require('./utilities');
33

4+
// https://reactjs.org/docs/uncontrolled-components.html
5+
// https://developer.mozilla.org/docs/Web/HTML/Attributes
6+
var UNCONTROLLED_COMPONENT_ATTRIBUTES = ['checked', 'value'];
7+
var UNCONTROLLED_COMPONENT_NAMES = ['input', 'select', 'textarea'];
8+
9+
var VALUE_ONLY_INPUTS = {
10+
reset: true,
11+
submit: true
12+
};
13+
414
/**
515
* Converts HTML/SVG DOM attributes to React props.
616
*
@@ -11,18 +21,13 @@ var utilities = require('./utilities');
1121
module.exports = function attributesToProps(attributes, nodeName) {
1222
attributes = attributes || {};
1323

14-
var valueOnlyInputs = {
15-
reset: true,
16-
submit: true
17-
};
18-
1924
var attributeName;
2025
var attributeNameLowerCased;
2126
var attributeValue;
2227
var propName;
2328
var propertyInfo;
2429
var props = {};
25-
var inputIsValueOnly = attributes.type && valueOnlyInputs[attributes.type];
30+
var inputIsValueOnly = attributes.type && VALUE_ONLY_INPUTS[attributes.type];
2631

2732
for (attributeName in attributes) {
2833
attributeValue = attributes[attributeName];
@@ -41,10 +46,9 @@ module.exports = function attributesToProps(attributes, nodeName) {
4146
propertyInfo = reactProperty.getPropertyInfo(propName);
4247

4348
// convert attribute to uncontrolled component prop (e.g., `value` to `defaultValue`)
44-
// https://reactjs.org/docs/uncontrolled-components.html
4549
if (
46-
(propName === 'checked' || propName === 'value') &&
47-
nodeName !== 'option' &&
50+
UNCONTROLLED_COMPONENT_ATTRIBUTES.indexOf(propName) !== -1 &&
51+
UNCONTROLLED_COMPONENT_NAMES.indexOf(nodeName) !== -1 &&
4852
!inputIsValueOnly
4953
) {
5054
propName = getPropName('default' + attributeNameLowerCased);

test/attributes-to-props.test.js

+26-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ it('returns empty object is argument is undefined', () => {
55
expect(attributesToProps()).toEqual({});
66
});
77

8+
it.each(['input', 'select', 'textarea'])(
9+
'converts uncontrolled component attributes',
10+
nodeName => {
11+
expect(
12+
attributesToProps({ value: 'foo', checked: false }, nodeName)
13+
).toEqual({
14+
defaultValue: 'foo',
15+
defaultChecked: true
16+
});
17+
}
18+
);
19+
20+
it.each(['button', 'data', 'li', 'meter', 'option', 'progress', 'param'])(
21+
'converts non-uncontrolled component attributes',
22+
nodeName => {
23+
expect(
24+
attributesToProps({ value: 'foo', checked: false }, nodeName)
25+
).toEqual({
26+
value: 'foo',
27+
checked: true
28+
});
29+
}
30+
);
31+
832
describe('attributesToProps with HTML attribute', () => {
933
it('converts attributes to React props', () => {
1034
const attributes = {
@@ -128,7 +152,7 @@ describe('attributesToProps with HTML attribute', () => {
128152
selected: '',
129153
truespeed: ''
130154
};
131-
expect(attributesToProps(attributes)).toMatchInlineSnapshot(`
155+
expect(attributesToProps(attributes, 'select')).toMatchInlineSnapshot(`
132156
{
133157
"allowFullScreen": true,
134158
"allowpaymentrequest": "",
@@ -183,7 +207,7 @@ describe('attributesToProps with HTML attribute', () => {
183207
])(
184208
'converts form attribute to uncontrolled component property',
185209
(attributes, props) => {
186-
expect(attributesToProps(attributes)).toEqual(props);
210+
expect(attributesToProps(attributes, 'input')).toEqual(props);
187211
}
188212
);
189213

test/data/html.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ module.exports = {
1717
customElement:
1818
'<custom-element class="myClass" custom-attribute="value" style="-o-transition: all .5s; line-height: 1;"></custom-element>',
1919
form: '<input type="text" value="foo" checked="checked">',
20+
list: '<ol><li>One</li><li value="2">Two</li></ol>',
2021
template: '<template><article><p>Test</p></article></template>'
2122
};

test/index.test.js

+15
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,21 @@ describe('HTMLReactParser', () => {
163163
`);
164164
});
165165

166+
it('parses list', () => {
167+
expect(parse(html.list)).toMatchInlineSnapshot(`
168+
<ol>
169+
<li>
170+
One
171+
</li>
172+
<li
173+
value="2"
174+
>
175+
Two
176+
</li>
177+
</ol>
178+
`);
179+
});
180+
166181
it('parses template', () => {
167182
expect(parse(html.template)).toMatchInlineSnapshot(`
168183
<template>

0 commit comments

Comments
 (0)