Repository: remarkablemark/html-react-parser (ts)

Issue:
Parsing <input> "checked" and "value"
Parsing `<input type="checkbox" checked />` gives this warning:
You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.


Parsning `<input type="text" value="initial value" />` gives this warning:
You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

We made an attempt to fix in the replace method:

```
if (node.type === 'tag' && node.name === 'input') {
	  if (node.attribs.type === 'text') {
	    node.attribs.defaultValue = node.attribs.value
	    delete node.attribs['value']
	  }
	  
	  if (node.attribs.type === 'checkbox') {
	    node.attribs.defaultChecked = node.attribs.checked
	    delete node.attribs['checked']
	  }
}
```

This seems to work, but not sure if it's the best. Maybe it would be good for support for this case in the package? I see there is something similar dom-to-react for textarea.

Pull request description:
fix(attributes-to-props): convert attribute to uncontrolled component prop
Fixes #321

## What is the motivation for this pull request?

fix(attributes-to-props): convert attribute to uncontrolled component prop

## What is the current behavior?

HTML DOM attributes (`checked` and `value`) are rendered as [controlled components](https://reactjs.org/docs/forms.html#controlled-components):

- `checked` -> `checked`
- `value` -> `value`

## What is the new behavior?

HTML DOM attributes (`checked` and `value`) are rendered as [uncontrolled components](https://reactjs.org/docs/uncontrolled-components.html):

- `checked` -> `defaultChecked`
- `value` -> `defaultValue`

## Checklist:

- [x] [Conventional Commits](https://www.conventionalcommits.org/)
- [x] Tests

Patch:
diff --git a/lib/attributes-to-props.js b/lib/attributes-to-props.js
index 186db4b..7682a6c 100644
--- a/lib/attributes-to-props.js
+++ b/lib/attributes-to-props.js
@@ -28,11 +28,19 @@ module.exports = function attributesToProps(attributes) {
 
     // convert HTML/SVG attribute to React prop
     attributeNameLowerCased = attributeName.toLowerCase();
-    propName = reactProperty.possibleStandardNames[attributeNameLowerCased];
+    propName = getPropName(attributeNameLowerCased);
 
     if (propName) {
-      props[propName] = attributeValue;
       propertyInfo = reactProperty.getPropertyInfo(propName);
+
+      // convert attribute to uncontrolled component prop (e.g., `value` to `defaultValue`)
+      // https://reactjs.org/docs/uncontrolled-components.html
+      if (propName === 'checked' || propName === 'value') {
+        propName = getPropName('default' + attributeNameLowerCased);
+      }
+
+      props[propName] = attributeValue;
+
       switch (propertyInfo && propertyInfo.type) {
         case reactProperty.BOOLEAN:
           props[propName] = true;
@@ -57,3 +65,13 @@ module.exports = function attributesToProps(attributes) {
 
   return props;
 };
+
+/**
+ * Gets prop name from lowercased attribute name.
+ *
+ * @param {string} attributeName - Lowercased attribute name.
+ * @return {string}
+ */
+function getPropName(attributeName) {
+  return reactProperty.possibleStandardNames[attributeName];
+}


Test patch:
diff --git a/test/__snapshots__/attributes-to-props.test.js.snap b/test/__snapshots__/attributes-to-props.test.js.snap
index 9536352..077ce4d 100644
--- a/test/__snapshots__/attributes-to-props.test.js.snap
+++ b/test/__snapshots__/attributes-to-props.test.js.snap
@@ -31,9 +31,9 @@ Object {
   "async": true,
   "autoFocus": true,
   "autoPlay": true,
-  "checked": true,
   "controls": true,
   "default": true,
+  "defaultChecked": true,
   "disabled": true,
   "draggable": "false",
   "formNoValidate": true,
diff --git a/test/__snapshots__/index.test.js.snap b/test/__snapshots__/index.test.js.snap
index 772788c..df0531b 100644
--- a/test/__snapshots__/index.test.js.snap
+++ b/test/__snapshots__/index.test.js.snap
@@ -110,6 +110,14 @@ exports[`HTMLReactParser parses empty <script> 1`] = `<script />`;
 
 exports[`HTMLReactParser parses empty <style> 1`] = `<style />`;
 
+exports[`HTMLReactParser parses form 1`] = `
+<input
+  defaultChecked={true}
+  defaultValue="foo"
+  type="text"
+/>
+`;
+
 exports[`HTMLReactParser parses multiple HTML elements 1`] = `
 Array [
   <p>
diff --git a/test/attributes-to-props.test.js b/test/attributes-to-props.test.js
index 7ac762c..7254c05 100644
--- a/test/attributes-to-props.test.js
+++ b/test/attributes-to-props.test.js
@@ -103,6 +103,18 @@ describe('attributesToProps with HTML attribute', () => {
   ])('converts overloaded boolean attribute: %p', (attributes, props) => {
     expect(attributesToProps(attributes)).toEqual(props);
   });
+
+  it.each([
+    [{ checked: '' }, { defaultChecked: true }],
+    [{ checked: 'checked' }, { defaultChecked: true }],
+    [{ value: '' }, { defaultValue: '' }],
+    [{ value: 'foo' }, { defaultValue: 'foo' }]
+  ])(
+    'converts form attribute to uncontrolled component property',
+    (attributes, props) => {
+      expect(attributesToProps(attributes)).toEqual(props);
+    }
+  );
 });
 
 describe('attributesToProps with SVG attribute', () => {
diff --git a/test/data/html.js b/test/data/html.js
index e065a88..d0aa880 100644
--- a/test/data/html.js
+++ b/test/data/html.js
@@ -15,5 +15,6 @@ module.exports = {
   doctype: '<!DOCTYPE html>',
   title: '<title><em>text</em></title>',
   customElement:
-    '<custom-element class="myClass" custom-attribute="value" style="-o-transition: all .5s; line-height: 1;"></custom-element>'
+    '<custom-element class="myClass" custom-attribute="value" style="-o-transition: all .5s; line-height: 1;"></custom-element>',
+  form: '<input type="text" value="foo" checked="checked">'
 };
diff --git a/test/index.test.js b/test/index.test.js
index eed100f..6118117 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -65,6 +65,10 @@ describe('HTMLReactParser', () => {
     expect(parse('<style></style>')).toMatchSnapshot();
   });
 
+  it('parses form', () => {
+    expect(parse(html.form)).toMatchSnapshot();
+  });
+
   it('parses SVG', () => {
     expect(parse(svg.complex)).toMatchSnapshot();
   });