TroyAlford / react-jsx-parser

A React component which can parse JSX and output rendered React Components.
MIT License
632 stars 103 forks source link

allowUnknownElements: true incompatible with SSR #100

Closed petertenhoor closed 5 years ago

petertenhoor commented 5 years ago

Hi there,

We use JsxParser in a Next.js application to render some React Components and HTML from a string and it does exactly that which is great. There is just one issue we ran in to today:

I'd like to set the allowUnknownElements prop to true to ensure React components which are not in my component library will not be rendered.

When I put the allowUnknownElements to true, our server side rendering throws an error in react-jsx-parser/source/components/JsxParser.js:162:36:

ReferenceError: document is not defined

It is triggered by this if statement which references the document:

 if (!allowUnknownElements && document.createElement(name) instanceof HTMLUnknownElement) {
        onError(new Error(`The tag <${name}> is unrecognized in this browser, and will not be rendered.`))
        return undefined
      }

Rendering the JsxParser only in the client is not an option for us, because the components have SEO value and should be in the source code.

Would it be possible to use a SSR friendly way to check if an element is in the given component array?

TroyAlford commented 5 years ago

@petertenhoor - hmm... this code works by asking the browser, itself, what elements it recognizes. It sounds like you want the even more restrictive componentsOnly.

<JsxParser
  components={{ ComponentA, ComponentB }}
  componentsOnly
  jsx={`
    <ComponentA>Text!</ComponentA>
    <div>Omitted</div>
    <ComponentB>More Text!</ComponentB>
  `}
/>

This should omit the div entirely, but never run that check above, because allowUnknownElements is still true. These lines are the current test which validates this functionality working. As you can see, it omits the h1 and h2 normal tags entirely, but still renders the div tags that are defined by the stated valid elements. The check in the code happens before the allowUnknownElements check as well, and will prevent the problematic line from being hit at all if enabled.

If that doesn't work for you, let me know and we can certainly work to fix it.

petertenhoor commented 5 years ago

Thank you for your response.

My JSX string has both HTML and react components in it, so componentsOnly results in the parser not rendering my html:

err while rendering jsx parser Error: The componenet <p> is unrecognized, and will not be rendered.

My API for example returns the following JSX / HTML string:

"\n<HeadlessRow marginBottom={80}>\n    \n<HeadlessColumn width={100}>\n    \n<HeadlessText colorTheme=\"dark\" fontType=\"light\">\n    <p>Zipcode finder</p>\n</HeadlessText>\n\n</HeadlessColumn>\n\n</HeadlessRow>\n<HeadlessRow rowStretch=\"stretch_row\" contentAlignment=\"middle\" backgroundColor=\"red\">\n    \n<HeadlessColumn width={50} marginBottom={40}>\n    \n<ImageShortcode  image=\"https://ribsdelivery.test/wp-content/uploads/2019/08/burger.png\" imageFull=\"https://ribsdelivery.test/wp-content/uploads/2019/08/burger.png\" maxWidth=\"100%\" align=\"left\" linkTarget=\"_self\" decorationType=\"waves\" decorationColor=\"yellow\" decorationPosition=\"top-left\">\n</ImageShortcode>\n</HeadlessColumn>\n\n<HeadlessColumn width={50} marginTop={80} marginBottom={80}>\n    \n<HeadlessText colorTheme=\"light\" fontType=\"bold\">\n    <h2>Je eigen<br />\nRibs Delivery</h2>\n<p>In onze bezorgrestaurants worden onze vleesgerechten vers klaargemaakt. Geen fastfood, maar gerechten van hoge kwaliteit en met zorg bereid.</p>\n</HeadlessText>\n<Button  text=\"Meer weten\" linkUrl=\"http://127.0.0.1:1337/franchise\" color=\"yellow-primary\" align=\"left\">\n</Button>\n\n</HeadlessColumn>\n\n</HeadlessRow>\n<HeadlessRow rowStretch=\"stretch_row\" contentAlignment=\"middle\" marginBottom={80} backgroundColor=\"yellow\">\n    \n<HeadlessColumn width={50} marginTop={80} marginBottom={80}>\n    \n<HeadlessText colorTheme=\"light\" fontType=\"bold\">\n    <h2>Bezorger<br />\nworden?</h2>\n<p>In onze bezorgrestaurants worden onze vleesgerechten vers klaargemaakt. Geen fastfood, maar gerechten van hoge kwaliteit en met zorg bereid.</p>\n</HeadlessText>\n<Button  text=\"Bekijk vacatures\" linkUrl=\"http://127.0.0.1:1337/over-ribs-delivery\" color=\"red-primary\" align=\"left\">\n</Button>\n\n</HeadlessColumn>\n\n<HeadlessColumn width={50} marginTop={40}>\n    \n<ImageShortcode  image=\"https://ribsdelivery.test/wp-content/uploads/2019/08/bezorger.png\" imageFull=\"https://ribsdelivery.test/wp-content/uploads/2019/08/bezorger.png\" maxWidth=\"100%\" align=\"right\" linkTarget=\"_self\" decorationType=\"zigzag\" decorationColor=\"red\" decorationPosition=\"bottom-right\">\n</ImageShortcode>\n</HeadlessColumn>\n\n</HeadlessRow>\n<HeadlessRow marginTop={80}>\n    \n<HeadlessColumn width={100}>\n    \n<HeadlessText colorTheme=\"dark\" fontType=\"light\">\n    <h1>Ribsdelivery<br />\nDelicious to the bone</h1>\n</HeadlessText>\n\n</HeadlessColumn>\n\n</HeadlessRow>\n<HeadlessRow marginBottom={80}>\n    \n<HeadlessColumn width={50}>\n    \n<HeadlessText colorTheme=\"dark\" fontType=\"light\">\n    <p>Ribsfactory Tilburg is gevestigd in het verlengde deel van de Heuvel, de hoofdwinkelstraat in het centrum van Tilburg. Dit laatste deel van de Heuvel wordt in de volksmond ook wel ‘korte’ Heuvel genoemd en maakt met zijn gezellige horecagelegenheden deel uit van het bruisende uitgaansleven van Tilburg. Vlakbij vindt je het befaamde poppodium 013 en ook de Pathé bioscoop ligt op looppafstand. In het winkelgebied van Tilburg is het elke week koopzondag. Je kunt een bezoek aan Ribsfactory dan ook uitstekend combineren met een dagje winkelen. Maar een heerlijk spareribsmenu is uiteraard ook een prima start van je avondje uit!</p>\n</HeadlessText>\n\n</HeadlessColumn>\n\n<HeadlessColumn width={50}>\n    \n<HeadlessText colorTheme=\"dark\" fontType=\"light\">\n    <p>Ribsfactory Tilburg is gevestigd in het verlengde deel van de Heuvel, de hoofdwinkelstraat in het centrum van Tilburg. Dit laatste deel van de Heuvel wordt in de volksmond ook wel ‘korte’ Heuvel genoemd en maakt met zijn gezellige horecagelegenheden deel uit van het bruisende uitgaansleven van Tilburg. Vlakbij vindt je het befaamde poppodium 013 en ook de Pathé bioscoop ligt op looppafstand. In het winkelgebied van Tilburg is het elke week koopzondag. Je kunt een bezoek aan Ribsfactory dan ook uitstekend combineren met een dagje winkelen. Maar een heerlijk spareribsmenu is uiteraard ook een prima start van je avondje uit!</p>\n</HeadlessText>\n\n</HeadlessColumn>\n\n</HeadlessRow>"

This is my React component calling react-jsx-parser:

import PropTypes from "prop-types";
import JsxParser from "react-jsx-parser";

import {shortcodeComponents} from "../../utils/shortcodeLibrary";

const ShortcodeContent = ({content}) => {
    return (
        <JsxParser renderInWrapper={false}
                   onError={(err) => console.log('err while rendering jsx parser', err)}
                   allowUnknownElements={true} //FIXME this should be false, but breaks SSR
                   componentsOnly={false}
                   components={shortcodeComponents}
                   jsx={content}/>
    )
}

ShortcodeContent.defaultProps = {
    content: ""
}

ShortcodeContent.propTypes = {
    content: PropTypes.string
}

export default ShortcodeContent;

This is my shortcodeLibrary.js which is the object I pass to react-jsx-parser as Components prop:

import dynamic from "next/dynamic";

const HeadlessRow = dynamic(() => import("../components/ShortcodeContent/HeadlessRow"))
const HeadlessColumn = dynamic(() => import("../components/ShortcodeContent/HeadlessColumn"))
const HeadlessText = dynamic(() => import("../components/ShortcodeContent/HeadlessText"))
const HeadlessLink = dynamic(() => import("../components/ShortcodeContent/HeadlessLink"))
const Button = dynamic(() => import("../components/ShortcodeContent/Button"))
const ImageShortcode = dynamic(() => import("../components/ShortcodeContent/Image"))
const GravityForm = dynamic(() => import("../components/ShortcodeContent/GravityForm"))

const shortcodeComponents = {
    HeadlessRow,
    HeadlessColumn,
    HeadlessText,
    HeadlessLink,
    Button,
    ImageShortcode,
    GravityForm,
}

export {shortcodeComponents}

I just wanted to make sure that if the API would give me an unknown React string, that it would not be rendered as a not existing HTML element. But as soon as I set allowUnknownElements to true, it gives me the document is not defined error. Which means it's not compatible with SSR.