Skip to content

Commit 20177ee

Browse files
Merge pull request #16 from remarkablemark/bug-void-element
Fix void element bug and add `key` parameter to `replace` method
2 parents 15d92ab + e3c91ac commit 20177ee

File tree

6 files changed

+85
-41
lines changed

6 files changed

+85
-41
lines changed

README.md

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,36 @@ ReactDOM.render(
6868

6969
### Options
7070

71-
#### replace(domNode)
71+
#### replace(domNode, key)
7272

73-
`replace` allows you to swap an element with your own React element.
73+
The `replace` method allows you to swap an element with your own React element.
7474

75-
The `domNode` object has the same schema as the output from [htmlparser2.parseDOM](https://github.com/fb55/domhandler#example).
75+
The method has 2 parameters:
76+
1. `domNode`: An object which shares the same schema as the output from [htmlparser2.parseDOM](https://github.com/fb55/domhandler#example).
77+
2. `key`: A number to keep track of the element. You should set it as the ["key" prop](https://fb.me/react-warning-keys) in case your element has siblings.
78+
79+
```js
80+
Parser('<p id="replace">text</p>', {
81+
replace: function(domNode, key) {
82+
console.log(domNode);
83+
// { type: 'tag',
84+
// name: 'p',
85+
// attribs: { id: 'replace' },
86+
// children: [],
87+
// next: null,
88+
// prev: null,
89+
// parent: null }
90+
91+
console.log(key); // 0
92+
93+
return;
94+
// element is not replaced because
95+
// a valid React element is not returned
96+
}
97+
});
98+
```
99+
100+
Working example:
76101

77102
```js
78103
var Parser = require('html-react-parser');
@@ -81,18 +106,14 @@ var React = require('react');
81106
var html = '<div><p id="main">replace me</p></div>';
82107

83108
var reactElement = Parser(html, {
84-
replace: function(domNode) {
85-
// example `domNode`:
86-
// { type: 'tag',
87-
// name: 'p',
88-
// attribs: { id: 'main' },
89-
// children: [],
90-
// next: null,
91-
// prev: null,
92-
// parent: [Circular] }
109+
replace: function(domNode, key) {
93110
if (domNode.attribs && domNode.attribs.id === 'main') {
94-
// element is replaced only if a valid React element is returned
95-
return React.createElement('span', { style: { fontSize: '42px' } }, 'replaced!');
111+
return React.createElement('span', {
112+
key: key,
113+
style: { fontSize: '42px' } },
114+
'replaced!');
115+
// equivalent jsx syntax:
116+
// return <span key={key} style={{ fontSize: '42px' }}>replaced!</span>;
96117
}
97118
}
98119
});

lib/dom-to-react.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function domToReact(nodes, options) {
2828

2929
// replace with custom React element (if applicable)
3030
if (isReplacePresent) {
31-
replacement = options.replace(node);
31+
replacement = options.replace(node, i); // i = key
3232

3333
if (React.isValidElement(replacement)) {
3434
result.push(replacement);
@@ -59,8 +59,8 @@ function domToReact(nodes, options) {
5959
if (node.name === 'textarea' && node.children[0]) {
6060
props.defaultValue = node.children[0].data;
6161

62-
} else if (node.children) {
63-
// continue recursion of creating React elements
62+
// continue recursion of creating React elements (if applicable)
63+
} else if (node.children && node.children.length) {
6464
children = domToReact(node.children, options);
6565
}
6666

@@ -69,7 +69,7 @@ function domToReact(nodes, options) {
6969
continue;
7070
}
7171

72-
// specify a `key` prop if returning an array
72+
// specify a "key" prop if element has siblings
7373
// https://fb.me/react-warning-keys
7474
if (len > 1) {
7575
props.key = i;

test/data.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
"multiple": "<p>foo</p><p>bar</p>",
55
"nested": "<div><p>foo <em>bar</em></p></div>",
66
"attributes": "<hr id=\"foo\" class=\"bar baz\" style=\"background: #fff; text-align: center;\" data-foo=\"bar\" />",
7-
"complex": "<html><head><title>Title</title></head><body><header id=\"header\">Header</header><h1 style=\"color:#000;font-size:42px;\">Heading</h1><p>Paragraph</p><div class=\"class1 class2\">Some <em>text</em>.</div><script>alert();</script></body></html>",
7+
"complex": "<html><head><meta charset=\"utf-8\"/><title>Title</title><link rel=\"stylesheet\" href=\"style.css\"/></head><body><header id=\"header\">Header</header><h1 style=\"color:#000;font-size:42px;\">Heading</h1><hr/><p>Paragraph</p><img src=\"image.jpg\"/><div class=\"class1 class2\">Some <em>text</em>.</div><script>alert();</script></body></html>",
88
"textarea": "<textarea>foo</textarea>",
99
"script": "<script>alert(1 < 2);</script>",
10+
"img": "<img src=\"http://stat.ic/img.jpg\" alt=\"Image\"/>",
11+
"void": "<link/><meta/><img/><br/><hr/><input/>",
1012
"comment": "<!-- comment -->"
1113
},
1214
"svg": {

test/dom-to-react.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var assert = require('assert');
77
var React = require('react');
88
var htmlToDOMServer = require('../lib/html-to-dom-server');
99
var domToReact = require('../lib/dom-to-react');
10+
var helpers = require('./helpers/');
1011
var data = require('./data');
1112

1213
/**
@@ -67,6 +68,20 @@ describe('dom-to-react parser', function() {
6768
);
6869
});
6970

71+
it('does not have `children` for void elements', function() {
72+
var html = data.html.img;
73+
var reactElement = domToReact(htmlToDOMServer(html));
74+
assert(!reactElement.props.children);
75+
});
76+
77+
it('does not throw an error for void elements', function() {
78+
var html = data.html.void;
79+
var reactElements = domToReact(htmlToDOMServer(html));
80+
assert.doesNotThrow(function() {
81+
helpers.render(React.createElement('div', {}, reactElements));
82+
});
83+
});
84+
7085
it('skips HTML comments', function() {
7186
var html = [data.html.single, data.html.comment, data.html.single].join('');
7287
var reactElements = domToReact(htmlToDOMServer(html));

test/helpers/index.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55
*/
66
var assert = require('assert');
77
var util = require('util');
8+
var React = require('react');
9+
var ReactDOMServer = require('react-dom/server');
10+
11+
/**
12+
* Render a React element to static HTML markup.
13+
*
14+
* @param {ReactElement} reactElement - The React element.
15+
* @return {String} - The static HTML markup.
16+
*/
17+
function render(reactElement) {
18+
if (!React.isValidElement(reactElement)) {
19+
throw new Error(reactElement, 'is not a valid React element.');
20+
}
21+
return ReactDOMServer.renderToStaticMarkup(reactElement);
22+
}
823

924
/**
1025
* Test for deep equality between objects that have circular references.
@@ -65,5 +80,6 @@ function deepEqualCircular(actual, expected) {
6580
* Export assert helpers.
6681
*/
6782
module.exports = {
68-
deepEqualCircular: deepEqualCircular
83+
deepEqualCircular: deepEqualCircular,
84+
render: render
6985
};

test/html-to-react.js

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,10 @@
55
*/
66
var assert = require('assert');
77
var React = require('react');
8-
var ReactDOMServer = require('react-dom/server');
98
var Parser = require('../');
9+
var helpers = require('./helpers/');
1010
var data = require('./data');
1111

12-
/**
13-
* Render a React element to static HTML markup.
14-
*
15-
* @param {ReactElement} reactElement - The React element.
16-
* @return {String} - The static HTML markup.
17-
*/
18-
function render(reactElement) {
19-
return ReactDOMServer.renderToStaticMarkup(reactElement);
20-
}
21-
2212
/**
2313
* Tests for `htmlToReact`.
2414
*/
@@ -44,35 +34,35 @@ describe('html-to-react', function() {
4434
it('converts single HTML element to React', function() {
4535
var html = data.html.single;
4636
var reactElement = Parser(html);
47-
assert.equal(render(reactElement), html);
37+
assert.equal(helpers.render(reactElement), html);
4838
});
4939

5040
it('converts single HTML element and ignores comment', function() {
5141
var html = data.html.single;
5242
// comment should be ignored
5343
var reactElement = Parser(html + data.html.comment);
54-
assert.equal(render(reactElement), html);
44+
assert.equal(helpers.render(reactElement), html);
5545
});
5646

5747
it('converts multiple HTML elements to React', function() {
5848
var html = data.html.multiple;
5949
var reactElements = Parser(html);
6050
assert.equal(
61-
render(React.createElement('div', {}, reactElements)),
51+
helpers.render(React.createElement('div', {}, reactElements)),
6252
'<div>' + html + '</div>'
6353
);
6454
});
6555

6656
it('converts complex HTML to React', function() {
6757
var html = data.html.complex;
6858
var reactElement = Parser(html);
69-
assert.equal(render(reactElement), html);
59+
assert.equal(helpers.render(reactElement), html);
7060
});
7161

7262
it('converts SVG to React', function() {
7363
var svg = data.svg.complex;
7464
var reactElement = Parser(svg);
75-
assert.equal(render(reactElement), svg);
65+
assert.equal(helpers.render(reactElement), svg);
7666
});
7767

7868
});
@@ -87,15 +77,15 @@ describe('html-to-react', function() {
8777
it('overrides the element if replace is valid', function() {
8878
var html = data.html.complex;
8979
var reactElement = Parser(html, {
90-
replace: function(node) {
80+
replace: function(node, key) {
9181
if (node.name === 'title') {
92-
return React.createElement('meta', { charSet: 'utf-8' });
82+
return React.createElement('title', { key: key }, 'Replaced Title');
9383
}
9484
}
9585
});
9686
assert.equal(
97-
render(reactElement),
98-
html.replace('<title>Title</title>', '<meta charset="utf-8"/>')
87+
helpers.render(reactElement),
88+
html.replace('<title>Title</title>', '<title>Replaced Title</title>')
9989
);
10090
});
10191

@@ -112,7 +102,7 @@ describe('html-to-react', function() {
112102
}
113103
});
114104
assert.notEqual(
115-
render(reactElement),
105+
helpers.render(reactElement),
116106
html.replace(
117107
'<header id="header">Header</header>',
118108
'<h1>Heading</h1>'

0 commit comments

Comments
 (0)