React dangerouslySetInnerHTML breaks JSON config with & character

1.3k views Asked by At

I am trying to put amp-analytics script into amp page generated from Next.js, but dangerouslySetInnerHTML breaks JSON config because of & character.

Here is the code:

<amp-analytics id='analytics1' type='googleanalytics'>
  <script type='application/json' dangerouslySetInnerHTML={{ __html: `
    {
      "vars": {
        "account": "XX-XXXXXXXX-X"
      },
      "requests": {
         "experiment": "\${pageview}&xid=\${xid}&xvar=\${xvar}"
      }
    }
  ` }} />
</amp-analytics>

And here is the result: enter image description here

As you can see the & character was converted to \u0026 and now amp-analytics tag doesn't work.

I also tried this:

<amp-analytics id='analytics1' type='googleanalytics'>
  <script type='application/json' dangerouslySetInnerHTML={{ __html: `
    {
      "vars": {
        "account": "XX-XXXXXXXX-X"
      },
      "requests": {
         "experiment": "${JSON.stringify('${pageview}&xid=${xid}&xvar=${xvar}')}"
      }
    }
  ` }} />
</amp-analytics>

But got not valid JSON (with right & symbol) enter image description here

Any ideas on how can I solve this?

As It turned out it happens because of AMP Optimizer in Next.js — github.com/ampproject/amp-toolbox/pull/649 And now I have to find a solution for that case

1

There are 1 answers

4
Lionel Rowe On

You don't need to use dangerouslySetInnerHTML, given that all you want to do is set the text content. dangerouslySetInnerHTML is required only if you need to parse HTML into DOM nodes.

You can set the text content like this:

<script type='application/json'>
  {JSON.stringify(
    {
      vars: {
        account: 'XX-XXXXXXXX-X',
      },
      requests: {
        experiment: `${pageview}&xid=${xid}&xvar=${xvar}`,
      },
    }
  )}
</script>

If you do it this way, React is smart enough to know that HTML entities aren't required in script tags, so those &s won't be escaped (unlike if it was a normal HTML element such as div ).

You can verify that like this (demo):

const InnerHtmlLogger = ({ TagName }) => {
  const ref = useRef(null);

  useEffect(() => {
    console.log(TagName, ref.current.innerHTML);
  }, [TagName]);

  return (
    <TagName ref={ref}>
      {JSON.stringify({
        '&': '&&',
        '&&&': ['&&&&', '&&&&&']
      })}
    </TagName>
  );
};

const App = () => {
  return (
    <>
      <InnerHtmlLogger TagName='div' />
      <InnerHtmlLogger TagName='script' />
    </>
  );
}

// Log output:
// div {"&amp;":"&amp;&amp;","&amp;&amp;&amp;":["&amp;&amp;&amp;&amp;","&amp;&amp;&amp;&amp;&amp;"]}
// script {"&":"&&","&&&":["&&&&","&&&&&"]}