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