Skip to content

Commit 8a6589f

Browse files
Merge pull request #162 from remarkablemark/feat/option-trim
feat: add option `trim`
2 parents 4ef89af + 21190a3 commit 8a6589f

File tree

6 files changed

+99
-26
lines changed

6 files changed

+99
-26
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,32 @@ See [htmlparser2 options](https://github.com/fb55/htmlparser2/wiki/Parser-option
252252

253253
> **Warning**: By overriding htmlparser2 options, there's a chance of breaking universal rendering. Do this at your own risk.
254254
255+
### trim
256+
257+
Normally, whitespace is preserved:
258+
259+
```js
260+
parse('<br>\n'); // [React.createElement('br'), '\n']
261+
```
262+
263+
By enabling the `trim` option, whitespace text nodes will be skipped:
264+
265+
```js
266+
parse('<br>\n', { trim: true }); // React.createElement('br')
267+
```
268+
269+
This addresses the warning:
270+
271+
```
272+
Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <table>. Make sure you don't have any extra whitespace between tags on each line of your source code.
273+
```
274+
275+
However, this option may strip out intentional whitespace:
276+
277+
```js
278+
parse('<p> </p>', { trim: true }); // React.createElement('p')
279+
```
280+
255281
## FAQ
256282

257283
#### Is this library XSS safe?
@@ -288,6 +314,10 @@ parse('<div /><div />'); // returns single element instead of array of elements
288314

289315
See [#158](https://github.com/remarkablemark/html-react-parser/issues/158).
290316

317+
#### I get "Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table."
318+
319+
Enable the [trim](https://github.com/remarkablemark/html-react-parser#trim) option. See [#155](https://github.com/remarkablemark/html-react-parser/issues/155).
320+
291321
## Benchmarks
292322

293323
```sh

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface HTMLReactParserOptions {
2121
replace?: (
2222
domNode: DomElement
2323
) => JSX.Element | object | void | undefined | null | false;
24+
25+
trim?: boolean;
2426
}
2527

2628
/**

lib/dom-to-react.js

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ function domToReact(nodes, options) {
2424
var replaceElement;
2525
var props;
2626
var children;
27+
var data;
28+
var trim = options.trim;
2729

2830
for (var i = 0, len = nodes.length; i < len; i++) {
2931
node = nodes[i];
@@ -33,7 +35,7 @@ function domToReact(nodes, options) {
3335
replaceElement = options.replace(node);
3436

3537
if (isValidElement(replaceElement)) {
36-
// specify a "key" prop if element has siblings
38+
// set "key" prop for sibling elements
3739
// https://fb.me/react-warning-keys
3840
if (len > 1) {
3941
replaceElement = cloneElement(replaceElement, {
@@ -46,45 +48,54 @@ function domToReact(nodes, options) {
4648
}
4749

4850
if (node.type === 'text') {
49-
result.push(node.data);
51+
// if trim option is enabled, skip whitespace text nodes
52+
if (trim) {
53+
data = node.data.trim();
54+
if (data) {
55+
result.push(node.data);
56+
}
57+
} else {
58+
result.push(node.data);
59+
}
5060
continue;
5161
}
5262

5363
props = node.attribs;
5464
if (!shouldPassAttributesUnaltered(node)) {
55-
// update values
5665
props = attributesToProps(node.attribs);
5766
}
5867

5968
children = null;
6069

61-
// node type for <script> is "script"
62-
// node type for <style> is "style"
63-
if (node.type === 'script' || node.type === 'style') {
64-
// prevent text in <script> or <style> from being escaped
65-
// https://facebook.github.io/react/tips/dangerously-set-inner-html.html
66-
if (node.children[0]) {
67-
props.dangerouslySetInnerHTML = {
68-
__html: node.children[0].data
69-
};
70-
}
71-
} else if (node.type === 'tag') {
72-
// setting textarea value in children is an antipattern in React
73-
// https://reactjs.org/docs/forms.html#the-textarea-tag
74-
if (node.name === 'textarea' && node.children[0]) {
75-
props.defaultValue = node.children[0].data;
76-
77-
// continue recursion of creating React elements (if applicable)
78-
} else if (node.children && node.children.length) {
79-
children = domToReact(node.children, options);
80-
}
70+
switch (node.type) {
71+
case 'script':
72+
case 'style':
73+
// prevent text in <script> or <style> from being escaped
74+
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
75+
if (node.children[0]) {
76+
props.dangerouslySetInnerHTML = {
77+
__html: node.children[0].data
78+
};
79+
}
80+
break;
81+
82+
case 'tag':
83+
// setting textarea value in children is an antipattern in React
84+
// https://reactjs.org/docs/forms.html#the-textarea-tag
85+
if (node.name === 'textarea' && node.children[0]) {
86+
props.defaultValue = node.children[0].data;
87+
} else if (node.children && node.children.length) {
88+
// continue recursion of creating React elements (if applicable)
89+
children = domToReact(node.children, options);
90+
}
91+
break;
8192

8293
// skip all other cases (e.g., comment)
83-
} else {
84-
continue;
94+
default:
95+
continue;
8596
}
8697

87-
// specify a "key" prop if element has siblings
98+
// set "key" prop for sibling elements
8899
// https://fb.me/react-warning-keys
89100
if (len > 1) {
90101
props.key = i;
@@ -97,6 +108,8 @@ function domToReact(nodes, options) {
97108
}
98109

99110
/**
111+
* Determines whether attributes should be altered or not.
112+
*
100113
* @param {React.ReactElement} node
101114
* @return {Boolean}
102115
*/

test/html-to-react.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,27 @@ describe('HTML to React', () => {
152152
);
153153
});
154154
});
155+
156+
describe('trim', () => {
157+
it('preserves whitespace text nodes when disabled (default)', () => {
158+
const html = `<table>
159+
<tbody>
160+
</tbody>
161+
</table>`;
162+
const reactElement = parse(html);
163+
assert.strictEqual(render(reactElement), html);
164+
});
165+
166+
it('removes whitespace text nodes when enabled', () => {
167+
const html = `<table>
168+
<tbody><tr><td> text </td><td> </td>\t</tr>\r</tbody>\n</table>`;
169+
const options = { trim: true };
170+
const reactElement = parse(html, options);
171+
assert.strictEqual(
172+
render(reactElement),
173+
'<table><tbody><tr><td> text </td><td></td></tr></tbody></table>'
174+
);
175+
});
176+
});
155177
});
156178
});

test/types/index.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ parse('<p/><p/>', {
7272
}
7373
});
7474

75+
// $ExpectType Element | Element[]
76+
parse('\t<p>text \r</p>\n', { trim: true });
77+
7578
// $ExpectType DomElement[]
7679
const domNodes = htmlToDOM('<div>text</div>');
7780

test/types/lib/dom-to-react.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ domToReact(htmlToDOM('<a id="header" href="#">Heading</a>'), {
3838
}
3939
}
4040
});
41+
42+
// $ExpectType Element | Element[]
43+
domToReact(htmlToDOM('\t<p>text \r</p>\n'), { trim: true });

0 commit comments

Comments
 (0)