1 | # html-react-parser
|
2 |
|
3 | [![NPM](https://nodei.co/npm/html-react-parser.png)](https://nodei.co/npm/html-react-parser/)
|
4 |
|
5 | [![NPM version](https://img.shields.io/npm/v/html-react-parser.svg)](https://www.npmjs.com/package/html-react-parser)
|
6 | [![Build Status](https://github.com/remarkablemark/html-react-parser/workflows/build/badge.svg?branch=master)](https://github.com/remarkablemark/html-react-parser/actions?query=workflow%3Abuild)
|
7 | [![codecov](https://codecov.io/gh/remarkablemark/html-react-parser/branch/master/graph/badge.svg?token=wosFd1DBIR)](https://codecov.io/gh/remarkablemark/html-react-parser)
|
8 | [![Dependency status](https://david-dm.org/remarkablemark/html-react-parser.svg)](https://david-dm.org/remarkablemark/html-react-parser)
|
9 | [![NPM downloads](https://img.shields.io/npm/dm/html-react-parser.svg?style=flat-square)](https://www.npmjs.com/package/html-react-parser)
|
10 | [![Discord](https://img.shields.io/discord/422421589582282752.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/njExwXdrRJ)
|
11 |
|
12 | HTML to React parser that works on both the server (Node.js) and the client (browser):
|
13 |
|
14 | ```
|
15 | HTMLReactParser(string[, options])
|
16 | ```
|
17 |
|
18 | The parser converts an HTML string to one or more [React elements](https://reactjs.org/docs/react-api.html#creating-react-elements).
|
19 |
|
20 | To replace an element with another element, check out the [`replace`](#replace) option.
|
21 |
|
22 | #### Example
|
23 |
|
24 | ```js
|
25 | const parse = require('html-react-parser');
|
26 | parse('<p>Hello, World!</p>'); // React.createElement('p', {}, 'Hello, World!')
|
27 | ```
|
28 |
|
29 | [Repl.it](https://repl.it/@remarkablemark/html-react-parser) | [JSFiddle](https://jsfiddle.net/remarkablemark/7v86d800/) | [CodeSandbox](https://codesandbox.io/s/940pov1l4w) | [TypeScript](https://codesandbox.io/s/html-react-parser-z0kp6) | [Examples](https://github.com/remarkablemark/html-react-parser/tree/master/examples)
|
30 |
|
31 | <details>
|
32 | <summary>Table of Contents</summary>
|
33 |
|
34 | - [Install](#install)
|
35 | - [Usage](#usage)
|
36 | - [replace](#replace)
|
37 | - [replace with TypeScript](#replace-with-typescript)
|
38 | - [replace element and children](#replace-element-and-children)
|
39 | - [replace element attributes](#replace-element-attributes)
|
40 | - [replace and remove element](#replace-and-remove-element)
|
41 | - [library](#library)
|
42 | - [htmlparser2](#htmlparser2)
|
43 | - [trim](#trim)
|
44 | - [Migration](#migration)
|
45 | - [v1.0.0](#v100)
|
46 | - [FAQ](#faq)
|
47 | - [Is this XSS safe?](#is-this-xss-safe)
|
48 | - [Does invalid HTML get sanitized?](#does-invalid-html-get-sanitized)
|
49 | - [Are `<script>` tags parsed?](#are-script-tags-parsed)
|
50 | - [Attributes aren't getting called](#attributes-arent-getting-called)
|
51 | - [Parser throws an error](#parser-throws-an-error)
|
52 | - [Is SSR supported?](#is-ssr-supported)
|
53 | - [Elements aren't nested correctly](#elements-arent-nested-correctly)
|
54 | - [Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table](#warning-validatedomnesting-whitespace-text-nodes-cannot-appear-as-a-child-of-table)
|
55 | - [Don't change case of tags](#dont-change-case-of-tags)
|
56 | - [TS Error: Property 'attribs' does not exist on type 'DOMNode'](#ts-error-property-attribs-does-not-exist-on-type-domnode)
|
57 | - [Can I enable `trim` for certain elements?](#can-i-enable-trim-for-certain-elements)
|
58 | - [Webpack build warnings](#webpack-build-warnings)
|
59 | - [Performance](#performance)
|
60 | - [Contributors](#contributors)
|
61 | - [Code Contributors](#code-contributors)
|
62 | - [Financial Contributors](#financial-contributors)
|
63 | - [Individuals](#individuals)
|
64 | - [Organizations](#organizations)
|
65 | - [Support](#support)
|
66 | - [License](#license)
|
67 |
|
68 | </details>
|
69 |
|
70 | ## Install
|
71 |
|
72 | [NPM](https://www.npmjs.com/package/html-react-parser):
|
73 |
|
74 | ```sh
|
75 | npm install html-react-parser --save
|
76 | ```
|
77 |
|
78 | [Yarn](https://yarnpkg.com/package/html-react-parser):
|
79 |
|
80 | ```sh
|
81 | yarn add html-react-parser
|
82 | ```
|
83 |
|
84 | [CDN](https://unpkg.com/html-react-parser/):
|
85 |
|
86 | ```html
|
87 | <!-- HTMLReactParser depends on React -->
|
88 | <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
|
89 | <script src="https://unpkg.com/html-react-parser@latest/dist/html-react-parser.min.js"></script>
|
90 | <script>
|
91 | window.HTMLReactParser(/* string */);
|
92 | </script>
|
93 | ```
|
94 |
|
95 | ## Usage
|
96 |
|
97 | Import or require the module:
|
98 |
|
99 | ```js
|
100 | // ES Modules
|
101 | import parse from 'html-react-parser';
|
102 |
|
103 | // CommonJS
|
104 | const parse = require('html-react-parser');
|
105 | ```
|
106 |
|
107 | Parse single element:
|
108 |
|
109 | ```js
|
110 | parse('<h1>single</h1>');
|
111 | ```
|
112 |
|
113 | Parse multiple elements:
|
114 |
|
115 | ```js
|
116 | parse('<li>Item 1</li><li>Item 2</li>');
|
117 | ```
|
118 |
|
119 | Make sure to render parsed adjacent elements under a parent element:
|
120 |
|
121 | ```jsx
|
122 | <ul>
|
123 | {parse(`
|
124 | <li>Item 1</li>
|
125 | <li>Item 2</li>
|
126 | `)}
|
127 | </ul>
|
128 | ```
|
129 |
|
130 | Parse nested elements:
|
131 |
|
132 | ```js
|
133 | parse('<body><p>Lorem ipsum</p></body>');
|
134 | ```
|
135 |
|
136 | Parse element with attributes:
|
137 |
|
138 | ```js
|
139 | parse(
|
140 | '<hr id="foo" class="bar" data-attr="baz" custom="qux" style="top:42px;">'
|
141 | );
|
142 | ```
|
143 |
|
144 | ### replace
|
145 |
|
146 | The `replace` option allows you to replace an element with another element.
|
147 |
|
148 | The `replace` callback's first argument is [domhandler](https://github.com/fb55/domhandler#example)'s node:
|
149 |
|
150 | ```js
|
151 | parse('<br>', {
|
152 | replace: domNode => {
|
153 | console.dir(domNode, { depth: null });
|
154 | }
|
155 | });
|
156 | ```
|
157 |
|
158 | Console output:
|
159 |
|
160 | ```js
|
161 | Element {
|
162 | type: 'tag',
|
163 | parent: null,
|
164 | prev: null,
|
165 | next: null,
|
166 | startIndex: null,
|
167 | endIndex: null,
|
168 | children: [],
|
169 | name: 'br',
|
170 | attribs: {}
|
171 | }
|
172 | ```
|
173 |
|
174 | The element is replaced if a **valid** React element is returned:
|
175 |
|
176 | ```jsx
|
177 | parse('<p id="replace">text</p>', {
|
178 | replace: domNode => {
|
179 | if (domNode.attribs && domNode.attribs.id === 'replace') {
|
180 | return <span>replaced</span>;
|
181 | }
|
182 | }
|
183 | });
|
184 | ```
|
185 |
|
186 | #### replace with TypeScript
|
187 |
|
188 | For TypeScript projects, you may need to check that `domNode` is an instance of domhandler's `Element`:
|
189 |
|
190 | ```tsx
|
191 | import { HTMLReactParserOptions } from 'html-react-parser';
|
192 | import { Element } from 'domhandler/lib/node';
|
193 |
|
194 | const options: HTMLReactParserOptions = {
|
195 | replace: domNode => {
|
196 | if (domNode instanceof Element && domNode.attribs) {
|
197 | // ...
|
198 | }
|
199 | }
|
200 | };
|
201 | ```
|
202 |
|
203 | If you're having issues with `domNode instanceof Element`, try this [alternative solution](https://github.com/remarkablemark/html-react-parser/issues/221#issuecomment-771600574).
|
204 |
|
205 | #### replace element and children
|
206 |
|
207 | Replace the element and its children (see [demo](https://repl.it/@remarkablemark/html-react-parser-replace-example)):
|
208 |
|
209 | ```jsx
|
210 | import parse, { domToReact } from 'html-react-parser';
|
211 |
|
212 | const html = `
|
213 | <p id="main">
|
214 | <span class="prettify">
|
215 | keep me and make me pretty!
|
216 | </span>
|
217 | </p>
|
218 | `;
|
219 |
|
220 | const options = {
|
221 | replace: ({ attribs, children }) => {
|
222 | if (!attribs) {
|
223 | return;
|
224 | }
|
225 |
|
226 | if (attribs.id === 'main') {
|
227 | return <h1 style={{ fontSize: 42 }}>{domToReact(children, options)}</h1>;
|
228 | }
|
229 |
|
230 | if (attribs.class === 'prettify') {
|
231 | return (
|
232 | <span style={{ color: 'hotpink' }}>
|
233 | {domToReact(children, options)}
|
234 | </span>
|
235 | );
|
236 | }
|
237 | }
|
238 | };
|
239 |
|
240 | parse(html, options);
|
241 | ```
|
242 |
|
243 | HTML output:
|
244 |
|
245 |
|
246 |
|
247 | ```html
|
248 | <h1 style="font-size:42px">
|
249 | <span style="color:hotpink">
|
250 | keep me and make me pretty!
|
251 | </span>
|
252 | </h1>
|
253 | ```
|
254 |
|
255 |
|
256 |
|
257 | #### replace element attributes
|
258 |
|
259 | Convert DOM attributes to React props with `attributesToProps`:
|
260 |
|
261 | ```jsx
|
262 | import parse, { attributesToProps } from 'html-react-parser';
|
263 |
|
264 | const html = `
|
265 | <main class="prettify" style="background: #fff; text-align: center;" />
|
266 | `;
|
267 |
|
268 | const options = {
|
269 | replace: domNode => {
|
270 | if (domNode.attribs && domNode.name === 'main') {
|
271 | const props = attributesToProps(domNode.attribs);
|
272 | return <div {...props} />;
|
273 | }
|
274 | }
|
275 | };
|
276 |
|
277 | parse(html, options);
|
278 | ```
|
279 |
|
280 | HTML output:
|
281 |
|
282 | ```html
|
283 | <div class="prettify" style="background:#fff;text-align:center"></div>
|
284 | ```
|
285 |
|
286 | #### replace and remove element
|
287 |
|
288 | [Exclude](https://repl.it/@remarkablemark/html-react-parser-56) an element from rendering by replacing it with `<React.Fragment>`:
|
289 |
|
290 | ```jsx
|
291 | parse('<p><br id="remove"></p>', {
|
292 | replace: ({ attribs }) => attribs && attribs.id === 'remove' && <></>
|
293 | });
|
294 | ```
|
295 |
|
296 | HTML output:
|
297 |
|
298 | ```html
|
299 | <p></p>
|
300 | ```
|
301 |
|
302 | ### library
|
303 |
|
304 | The `library` option specifies the UI library. The default library is **React**.
|
305 |
|
306 | To use Preact:
|
307 |
|
308 | ```js
|
309 | parse('<br>', {
|
310 | library: require('preact')
|
311 | });
|
312 | ```
|
313 |
|
314 | Or a custom library:
|
315 |
|
316 | ```js
|
317 | parse('<br>', {
|
318 | library: {
|
319 | cloneElement: () => {
|
320 | /* ... */
|
321 | },
|
322 | createElement: () => {
|
323 | /* ... */
|
324 | },
|
325 | isValidElement: () => {
|
326 | /* ... */
|
327 | }
|
328 | }
|
329 | });
|
330 | ```
|
331 |
|
332 | ### htmlparser2
|
333 |
|
334 | > `htmlparser2` options **do not work on the client-side** (browser) and **only works on the server-side** (Node.js). By overriding `htmlparser2` options, universal rendering can break.
|
335 |
|
336 | Default [htmlparser2 options](https://github.com/fb55/htmlparser2/wiki/Parser-options#option-xmlmode) can be overridden in >=[0.12.0](https://github.com/remarkablemark/html-react-parser/tree/v0.12.0).
|
337 |
|
338 | To enable [`xmlMode`](https://github.com/fb55/htmlparser2/wiki/Parser-options#option-xmlmode):
|
339 |
|
340 | ```js
|
341 | parse('<p /><p />', {
|
342 | htmlparser2: {
|
343 | xmlMode: true
|
344 | }
|
345 | });
|
346 | ```
|
347 |
|
348 | ### trim
|
349 |
|
350 | By default, whitespace is preserved:
|
351 |
|
352 | ```js
|
353 | parse('<br>\n'); // [React.createElement('br'), '\n']
|
354 | ```
|
355 |
|
356 | To remove whitespace, enable the `trim` option:
|
357 |
|
358 | ```js
|
359 | parse('<br>\n', { trim: true }); // React.createElement('br')
|
360 | ```
|
361 |
|
362 | This fixes the warning:
|
363 |
|
364 | ```
|
365 | 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.
|
366 | ```
|
367 |
|
368 | However, intentional whitespace may be stripped out:
|
369 |
|
370 | ```js
|
371 | parse('<p> </p>', { trim: true }); // React.createElement('p')
|
372 | ```
|
373 |
|
374 | ## Migration
|
375 |
|
376 | ### v1.0.0
|
377 |
|
378 | TypeScript projects will need to update the types in [v1.0.0](https://github.com/remarkablemark/html-react-parser/releases/tag/v1.0.0).
|
379 |
|
380 | For the `replace` option, you may need to do the following:
|
381 |
|
382 | ```tsx
|
383 | import { Element } from 'domhandler/lib/node';
|
384 |
|
385 | parse('<br class="remove">', {
|
386 | replace: domNode => {
|
387 | if (domNode instanceof Element && domNode.attribs.class === 'remove') {
|
388 | return <></>;
|
389 | }
|
390 | }
|
391 | });
|
392 | ```
|
393 |
|
394 | Since [v1.1.1](https://github.com/remarkablemark/html-react-parser/releases/tag/v1.1.1), Internet Explorer 9 (IE9) is no longer supported.
|
395 |
|
396 | ## FAQ
|
397 |
|
398 | ### Is this XSS safe?
|
399 |
|
400 | No, this library is _**not**_ [XSS (cross-site scripting)](https://wikipedia.org/wiki/Cross-site_scripting) safe. See [#94](https://github.com/remarkablemark/html-react-parser/issues/94).
|
401 |
|
402 | ### Does invalid HTML get sanitized?
|
403 |
|
404 | No, this library does _**not**_ sanitize HTML. See [#124](https://github.com/remarkablemark/html-react-parser/issues/124), [#125](https://github.com/remarkablemark/html-react-parser/issues/125), and [#141](https://github.com/remarkablemark/html-react-parser/issues/141).
|
405 |
|
406 | ### Are `<script>` tags parsed?
|
407 |
|
408 | Although `<script>` tags and their contents are rendered on the server-side, they're not evaluated on the client-side. See [#98](https://github.com/remarkablemark/html-react-parser/issues/98).
|
409 |
|
410 | ### Attributes aren't getting called
|
411 |
|
412 | The reason why your HTML attributes aren't getting called is because [inline event handlers](https://developer.mozilla.org/docs/Web/Guide/Events/Event_handlers) (e.g., `onclick`) are parsed as a _string_ rather than a _function_. See [#73](https://github.com/remarkablemark/html-react-parser/issues/73).
|
413 |
|
414 | ### Parser throws an error
|
415 |
|
416 | If the parser throws an erorr, check if your arguments are valid. See ["Does invalid HTML get sanitized?"](#does-invalid-html-get-sanitized).
|
417 |
|
418 | ### Is SSR supported?
|
419 |
|
420 | Yes, server-side rendering on Node.js is supported by this library. See [demo](https://repl.it/@remarkablemark/html-react-parser-SSR).
|
421 |
|
422 | ### Elements aren't nested correctly
|
423 |
|
424 | If your elements are nested incorrectly, check to make sure your [HTML markup is valid](https://validator.w3.org/). The HTML to DOM parsing will be affected if you're using self-closing syntax (`/>`) on non-void elements:
|
425 |
|
426 | ```js
|
427 | parse('<div /><div />'); // returns single element instead of array of elements
|
428 | ```
|
429 |
|
430 | See [#158](https://github.com/remarkablemark/html-react-parser/issues/158).
|
431 |
|
432 | ### Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table
|
433 |
|
434 | Enable the [trim](#trim) option. See [#155](https://github.com/remarkablemark/html-react-parser/issues/155).
|
435 |
|
436 | ### Don't change case of tags
|
437 |
|
438 | Tags are lowercased by default. To prevent that from happening, pass the [htmlparser2 option](#htmlparser2):
|
439 |
|
440 | ```js
|
441 | const options = {
|
442 | htmlparser2: {
|
443 | lowerCaseTags: false
|
444 | }
|
445 | };
|
446 | parse('<CustomElement>', options); // React.createElement('CustomElement')
|
447 | ```
|
448 |
|
449 | > **Warning**: By preserving case-sensitivity of the tags, you may get rendering warnings like:
|
450 | >
|
451 | > ```
|
452 | > Warning: <CustomElement> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.
|
453 | > ```
|
454 |
|
455 | See [#62](https://github.com/remarkablemark/html-react-parser/issues/62) and [example](https://repl.it/@remarkablemark/html-react-parser-62).
|
456 |
|
457 | ### TS Error: Property 'attribs' does not exist on type 'DOMNode'
|
458 |
|
459 | The TypeScript error occurs because `DOMNode` needs be an instance of domhandler's `Element`. See [migration](#migration) or [#199](https://github.com/remarkablemark/html-react-parser/issues/199).
|
460 |
|
461 | ### Can I enable `trim` for certain elements?
|
462 |
|
463 | Yes, you can enable or disable [`trim`](#trim) for certain elements using the [`replace`](#replace) option. See [#205](https://github.com/remarkablemark/html-react-parser/issues/205).
|
464 |
|
465 | ### Webpack build warnings
|
466 |
|
467 | If you see the Webpack build warning:
|
468 |
|
469 | ```
|
470 | export 'default' (imported as 'parse') was not found in 'html-react-parser'
|
471 | ```
|
472 |
|
473 | Then update your Webpack config to:
|
474 |
|
475 | ```js
|
476 | // webpack.config.js
|
477 | module.exports = {
|
478 | // ...
|
479 | resolve: {
|
480 | mainFields: ['browser', 'main', 'module']
|
481 | }
|
482 | };
|
483 | ```
|
484 |
|
485 | See [#238](https://github.com/remarkablemark/html-react-parser/issues/238) and [#213](https://github.com/remarkablemark/html-react-parser/issues/213).
|
486 |
|
487 | ## Performance
|
488 |
|
489 | Run benchmark:
|
490 |
|
491 | ```sh
|
492 | npm run test:benchmark
|
493 | ```
|
494 |
|
495 | Output of benchmark run on MacBook Pro 2017:
|
496 |
|
497 | ```
|
498 | html-to-react - Single x 415,186 ops/sec ±0.92% (85 runs sampled)
|
499 | html-to-react - Multiple x 139,780 ops/sec ±2.32% (87 runs sampled)
|
500 | html-to-react - Complex x 8,118 ops/sec ±2.99% (82 runs sampled)
|
501 | ```
|
502 |
|
503 | Run [Size Limit](https://github.com/ai/size-limit):
|
504 |
|
505 | ```sh
|
506 | npx size-limit
|
507 | ```
|
508 |
|
509 | ## Contributors
|
510 |
|
511 | ### Code Contributors
|
512 |
|
513 | This project exists thanks to all the people who contribute. [[Contribute](https://github.com/remarkablemark/html-react-parser/blob/master/CONTRIBUTING.md)].
|
514 |
|
515 | [![Code Contributors](https://opencollective.com/html-react-parser/contributors.svg?width=890&button=false)](https://github.com/remarkablemark/html-react-parser/graphs/contributors)
|
516 |
|
517 | ### Financial Contributors
|
518 |
|
519 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/html-react-parser/contribute)]
|
520 |
|
521 | #### Individuals
|
522 |
|
523 | [![Financial Contributors - Individuals](https://opencollective.com/html-react-parser/individuals.svg?width=890)](https://opencollective.com/html-react-parser)
|
524 |
|
525 | #### Organizations
|
526 |
|
527 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/html-react-parser/contribute)]
|
528 |
|
529 | [![Financial Contributors - Organization 0](https://opencollective.com/html-react-parser/organization/0/avatar.svg)](https://opencollective.com/html-react-parser/organization/0/website)
|
530 | [![Financial Contributors - Organization 1](https://opencollective.com/html-react-parser/organization/1/avatar.svg)](https://opencollective.com/html-react-parser/organization/1/website)
|
531 | [![Financial Contributors - Organization 2](https://opencollective.com/html-react-parser/organization/2/avatar.svg)](https://opencollective.com/html-react-parser/organization/2/website)
|
532 | [![Financial Contributors - Organization 3](https://opencollective.com/html-react-parser/organization/3/avatar.svg)](https://opencollective.com/html-react-parser/organization/3/website)
|
533 | [![Financial Contributors - Organization 4](https://opencollective.com/html-react-parser/organization/4/avatar.svg)](https://opencollective.com/html-react-parser/organization/4/website)
|
534 | [![Financial Contributors - Organization 5](https://opencollective.com/html-react-parser/organization/5/avatar.svg)](https://opencollective.com/html-react-parser/organization/5/website)
|
535 | [![Financial Contributors - Organization 6](https://opencollective.com/html-react-parser/organization/6/avatar.svg)](https://opencollective.com/html-react-parser/organization/6/website)
|
536 | [![Financial Contributors - Organization 7](https://opencollective.com/html-react-parser/organization/7/avatar.svg)](https://opencollective.com/html-react-parser/organization/7/website)
|
537 | [![Financial Contributors - Organization 8](https://opencollective.com/html-react-parser/organization/8/avatar.svg)](https://opencollective.com/html-react-parser/organization/8/website)
|
538 | [![Financial Contributors - Organization 9](https://opencollective.com/html-react-parser/organization/9/avatar.svg)](https://opencollective.com/html-react-parser/organization/9/website)
|
539 |
|
540 | ## Support
|
541 |
|
542 | - [GitHub Sponsors](https://b.remarkabl.org/github-sponsors)
|
543 | - [Open Collective](https://b.remarkabl.org/open-collective-html-react-parser)
|
544 | - [Tidelift](https://b.remarkabl.org/tidelift-html-react-parser)
|
545 | - [Patreon](https://b.remarkabl.org/patreon)
|
546 | - [Ko-fi](https://b.remarkabl.org/ko-fi)
|
547 | - [Liberapay](https://b.remarkabl.org/liberapay)
|
548 | - [Teepsring](https://b.remarkabl.org/teespring)
|
549 |
|
550 | ## License
|
551 |
|
552 | [MIT](https://github.com/remarkablemark/html-react-parser/blob/master/LICENSE)
|