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