From 7a7c08b3e7a656d01ebc44c30bb653dba28aacc1 Mon Sep 17 00:00:00 2001 From: literat Date: Sun, 5 Nov 2023 08:17:01 +0100 Subject: [PATCH] Fix(web-react): Icon component supports SSR * `dangerouslySetInnerHtml` do not work while rendering on server * this `Warning: Prop `dangerouslySetInnerHTML` did not match.` error was raised * we can avoid this by wrapping the component with something like `` * @see: https://stackoverflow.com/questions/64079321/react-tooltip-and-next-js-ssr-issue * or by using some library like ReactHtmlParser to render HTML string magically as a component * @see: https://www.npmjs.com/package/react-html-parser --- packages/web-react/package.json | 2 + .../web-react/src/components/Icon/Icon.tsx | 17 ++++- .../web-react/src/components/Icon/README.md | 28 +++++++- packages/web-react/src/utils/htmlParser.ts | 3 + yarn.lock | 70 ++++++++++++++++--- 5 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 packages/web-react/src/utils/htmlParser.ts diff --git a/packages/web-react/package.json b/packages/web-react/package.json index 9d2d3ecef4..3386b44d6e 100644 --- a/packages/web-react/package.json +++ b/packages/web-react/package.json @@ -58,6 +58,7 @@ "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-standard": "5.0.0", + "html-react-parser": "^5.0.6", "jest": "29.7.0", "jest-cli": "29.7.0", "jest-environment-jsdom": "29.7.0", @@ -79,6 +80,7 @@ "peerDependencies": { "@lmc-eu/spirit-icons": "*", "@lmc-eu/spirit-web": "*", + "html-react-parser": "^5.0.6", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" }, diff --git a/packages/web-react/src/components/Icon/Icon.tsx b/packages/web-react/src/components/Icon/Icon.tsx index bb793da3ae..33e5b440af 100644 --- a/packages/web-react/src/components/Icon/Icon.tsx +++ b/packages/web-react/src/components/Icon/Icon.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useIcon, useStyleProps } from '../../hooks'; import { IconProps } from '../../types'; +import { htmlParser } from '../../utils/htmlParser'; const defaultProps = { ariaHidden: true, @@ -16,6 +17,14 @@ export const Icon = (props: IconProps): JSX.Element => { icon = `${title}${icon}`; } + // @deprecated Usage of `html-react-parser` will be required in the next major version. + if (typeof window === 'undefined' && htmlParser == null) { + // eslint-disable-next-line no-console + console.warn( + 'Icon component is not supported in SSR without use of `html-react-parser`. Please install, missing peer dependency.', + ); + } + return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: Incompatible HTMLElement and SVGSVGElement @@ -24,13 +33,15 @@ export const Icon = (props: IconProps): JSX.Element => { fill="none" width={boxSize} height={boxSize} - // eslint-disable-next-line react/no-danger - dangerouslySetInnerHTML={{ __html: icon }} aria-hidden={ariaHidden} + // @deprecated Usage of dangerouslySetInnerHTML is discouraged due to support of SSR and will be removed in the next major version. + {...(typeof window !== 'undefined' ? { dangerouslySetInnerHTML: { __html: icon } } : {})} {...otherProps} {...styleProps} className={styleProps.className} - /> + > + {typeof window === 'undefined' && htmlParser != null ? htmlParser(icon) : null} + ); }; diff --git a/packages/web-react/src/components/Icon/README.md b/packages/web-react/src/components/Icon/README.md index a600443f7d..0d7c320d11 100644 --- a/packages/web-react/src/components/Icon/README.md +++ b/packages/web-react/src/components/Icon/README.md @@ -1,6 +1,30 @@ # Icon -Icons are graphical metaphors or symbols that can be used to compliment existing experiences. +Icons are graphical metaphors or symbols that can be used to complement existing experiences. + +## 🚀 Getting started + +To use this component in your project you need to run the following command using [npm](https://www.npmjs.com/): + +```bash +npm install -S @lcm-eu/spirit-icons html-react-parser +``` + +If you prefer [Yarn](https://yarnpkg.com/), use the following command instead: + +```bash +yarn add @lcm-eu/spirit-icons html-react-parser +``` + +### 📦 Dependencies + +Both packages are required as **peer dependency** to keep package size as low as possible. +So they will not be automatically installed with `@lmc-eu/spirit-web-react`. + +- [`@lmc-eu/spirit-icons`](https://github.com/lmc-eu/spirit-design-system/tree/main/packages/icons) - Spirit Icons package +- [`html-react-parser`](https://www.npmjs.com/package/html-react-parser) - HTML to React parser (avoid usage of `dangerouslySetInnerHTML` on the server side) + +## 📝 Usage ```jsx import { Icon, IconsProvider } from '@lmc-eu/spirit-web-react/components'; @@ -14,7 +38,7 @@ import icons from '@lmc-eu/spirit-icons/icons'; ``` -## API +## 🧩 API | Name | Type | Default | Required | Description | | ------------------ | --------------- | ------- | -------- | ------------------------- | diff --git a/packages/web-react/src/utils/htmlParser.ts b/packages/web-react/src/utils/htmlParser.ts new file mode 100644 index 0000000000..a2400377f1 --- /dev/null +++ b/packages/web-react/src/utils/htmlParser.ts @@ -0,0 +1,3 @@ +import parse from 'html-react-parser'; + +export const htmlParser = typeof window === 'undefined' ? parse : null; diff --git a/yarn.lock b/yarn.lock index 52b7e7dad0..36a1963098 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11126,6 +11126,13 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +domhandler@5.0.3, domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -11133,13 +11140,6 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" @@ -11156,7 +11156,7 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -domutils@^3.0.1: +domutils@^3.0.1, domutils@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== @@ -11400,7 +11400,7 @@ entities@^2.0.0, entities@^2.2.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.2.0: +entities@^4.2.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -14355,6 +14355,14 @@ hot-shots@10.0.0: optionalDependencies: unix-dgram "2.x" +html-dom-parser@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-5.0.4.tgz#2941a762317d088e747db31c8cf290987ec30a55" + integrity sha512-azy8THLKd4Ar0OVJpEgX+MSjYvKdNDWlGiRBIlovMqEQYMAnLLXBhhiSwjylDD3RDdcCYT8Utg6uoRDeLHUyHg== + dependencies: + domhandler "5.0.3" + htmlparser2 "9.0.0" + html-encoding-sniffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" @@ -14367,6 +14375,16 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-react-parser@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-5.0.6.tgz#ee9e8ae404aa38cfbaf3825cb26fabced46024a1" + integrity sha512-aPSzFhO2bK/L/mYQbMwFz+1QG8nhxozbwENt/52HTApasrBvDX87MFD5uSQIjq7Io0bnKzH4uh7PM42zZ4ag9A== + dependencies: + domhandler "5.0.3" + html-dom-parser "5.0.4" + react-property "2.0.2" + style-to-js "1.1.9" + html-tags@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" @@ -14377,6 +14395,16 @@ html-tags@^3.3.1: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== +htmlparser2@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.0.0.tgz#e431142b7eeb1d91672742dea48af8ac7140cddb" + integrity sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" @@ -14672,6 +14700,11 @@ init-package-json@3.0.2, init-package-json@^3.0.2: validate-npm-package-license "^3.0.4" validate-npm-package-name "^4.0.0" +inline-style-parser@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.2.tgz#d498b4e6de0373458fc610ff793f6b14ebf45633" + integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ== + inquirer-autocomplete-prompt@^1.0.1: version "1.4.0" resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.4.0.tgz#e767592f747e3d5bb6336fe71fb4094352e4c317" @@ -20578,6 +20611,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.0.0.tgz#026f6c4a27dbe33bf4a35655b9e1327c4e55e3f5" integrity sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw== +react-property@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6" + integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug== + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -22599,6 +22637,20 @@ style-search@^0.1.0: resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= +style-to-js@1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.9.tgz#5bdc23ba7624016094a19d6ea90fa3f98bee34c4" + integrity sha512-6bkwhOlPOx+wKiHVlPTHjUqM4zDKv9pyccehB8zetZL0hhQ7MVp7UEWUsohjiMdxwhSsEdjMrEve+8qzUVmY4w== + dependencies: + style-to-object "1.0.4" + +style-to-object@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.4.tgz#496fded508ce520eca13e738e8af33b8b5af98f2" + integrity sha512-KyNO6mfijxSnypdvEjeXlhvbGPSh0l1zBJp80n+ncBQvrEbSwBHwZCpo0xz6Q4AKSPfXowWwypCBAUAdfz3rFQ== + dependencies: + inline-style-parser "0.2.2" + stylelint-config-prettier@9.0.5: version "9.0.5" resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-9.0.5.tgz#9f78bbf31c7307ca2df2dd60f42c7014ee9da56e"