Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,32 @@ See [htmlparser2 options](https://github.com/fb55/htmlparser2/wiki/Parser-option

> **Warning**: By overriding htmlparser2 options, there's a chance of breaking universal rendering. Do this at your own risk.

### trim
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Normally, whitespace is preserved:

```js
parse('<br>\n'); // [React.createElement('br'), '\n']
```

By enabling the `trim` option, whitespace text nodes will be skipped:

```js
parse('<br>\n', { trim: true }); // React.createElement('br')
```

This addresses the warning:

```
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.
```

However, this option may strip out intentional whitespace:

```js
parse('<p> </p>', { trim: true }); // React.createElement('p')
```

## FAQ

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

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

#### I get "Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table."

Enable the [trim](https://github.com/remarkablemark/html-react-parser#trim) option. See [#155](https://github.com/remarkablemark/html-react-parser/issues/155).

## Benchmarks

```sh
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface HTMLReactParserOptions {
replace?: (
domNode: DomElement
) => JSX.Element | object | void | undefined | null | false;

trim?: boolean;
}

/**
Expand Down
65 changes: 39 additions & 26 deletions lib/dom-to-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ function domToReact(nodes, options) {
var replaceElement;
var props;
var children;
var data;
var trim = options.trim;

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

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

if (node.type === 'text') {
result.push(node.data);
// if trim option is enabled, skip whitespace text nodes
if (trim) {
data = node.data.trim();
if (data) {
result.push(node.data);
}
} else {
result.push(node.data);
}
continue;
}

props = node.attribs;
if (!shouldPassAttributesUnaltered(node)) {
// update values
props = attributesToProps(node.attribs);
}

children = null;

// node type for <script> is "script"
// node type for <style> is "style"
if (node.type === 'script' || node.type === 'style') {
// prevent text in <script> or <style> from being escaped
// https://facebook.github.io/react/tips/dangerously-set-inner-html.html
if (node.children[0]) {
props.dangerouslySetInnerHTML = {
__html: node.children[0].data
};
}
} else if (node.type === 'tag') {
// setting textarea value in children is an antipattern in React
// https://reactjs.org/docs/forms.html#the-textarea-tag
if (node.name === 'textarea' && node.children[0]) {
props.defaultValue = node.children[0].data;

// continue recursion of creating React elements (if applicable)
} else if (node.children && node.children.length) {
children = domToReact(node.children, options);
}
switch (node.type) {
case 'script':
case 'style':
// prevent text in <script> or <style> from being escaped
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
if (node.children[0]) {
props.dangerouslySetInnerHTML = {
__html: node.children[0].data
};
}
break;

case 'tag':
// setting textarea value in children is an antipattern in React
// https://reactjs.org/docs/forms.html#the-textarea-tag
if (node.name === 'textarea' && node.children[0]) {
props.defaultValue = node.children[0].data;
} else if (node.children && node.children.length) {
// continue recursion of creating React elements (if applicable)
children = domToReact(node.children, options);
}
break;

// skip all other cases (e.g., comment)
} else {
continue;
default:
continue;
}

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

/**
* Determines whether attributes should be altered or not.
*
* @param {React.ReactElement} node
* @return {Boolean}
*/
Expand Down
22 changes: 22 additions & 0 deletions test/html-to-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,27 @@ describe('HTML to React', () => {
);
});
});

describe('trim', () => {
it('preserves whitespace text nodes when disabled (default)', () => {
const html = `<table>
<tbody>
</tbody>
</table>`;
const reactElement = parse(html);
assert.strictEqual(render(reactElement), html);
});

it('removes whitespace text nodes when enabled', () => {
const html = `<table>
<tbody><tr><td> text </td><td> </td>\t</tr>\r</tbody>\n</table>`;
const options = { trim: true };
const reactElement = parse(html, options);
assert.strictEqual(
render(reactElement),
'<table><tbody><tr><td> text </td><td></td></tr></tbody></table>'
);
});
});
});
});
3 changes: 3 additions & 0 deletions test/types/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ parse('<p/><p/>', {
}
});

// $ExpectType Element | Element[]
parse('\t<p>text \r</p>\n', { trim: true });

// $ExpectType DomElement[]
const domNodes = htmlToDOM('<div>text</div>');

Expand Down
3 changes: 3 additions & 0 deletions test/types/lib/dom-to-react.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ domToReact(htmlToDOM('<a id="header" href="#">Heading</a>'), {
}
}
});

// $ExpectType Element | Element[]
domToReact(htmlToDOM('\t<p>text \r</p>\n'), { trim: true });