Skip to main content

How to handle script tags in React

The author of this blog doing a pose

Oscar Mejia Bautista @odm275

Script Tags in React?
Last updated: June 27, 2024

If you’ve used react, you know React lets you render html into the DOM using JSX. You can use any html tag, but have you ever tried to render a script tag?

Say for example, you want to set your google analytics script tag.

import React from "react";
const App = () => {
return (
<body>
<h1>This is my web page!</h1>
<script src="gpt.js" />
</body>
);
};
export default App;

As you have guessed, this won’t work.

It is an over simplification but React uses innerHTML under the hood when it is going to commit updates to the DOM.

body.innerHTML = `
<div>
<h1>This is my web page!</h1>
<script src="gpt.js" />
</div>
`;

The problem here the browser does not consider this safe(Read More Here). This should not be too surprising, since it looks like it could allow for cross-site scripting (XSS) attacks. Not quite, the browsers know to not run any script tags inserted with innerHTML, so while this is not going open you to vulnerabilities, it gets you closer to one. So actually your script tag, will make it to the DOM, but it won’t do anything(Read More Here)!

Hence, you’ll actually see your script tag in the DOM, but it won’t do anything. Well that doesn’t help you out much. So how do you add your script tag in react?

You can do this easily by using a library like react-helmet,

<Helmet>
<link rel="canonical" href="http://mysite.com/example" />
<script src="gpt.js" />
</Helmet>

and this will work. If this is all you need do then it is a great solution. However, say what if you only want to track users if they give you their permission to do so. Maybe you’d do something like.

import React, { useState } from "react";
const App = () => {
const [consent, setConsent] = useState(false);
return (
<>
<Helmet>
<link rel="canonical" href="http://mysite.com/example" />
{consent && <script src="gpt.js" />}
</Helmet>
<body>
<h1>This is my web page!</h1>
</body>
</>
);
};
export default App;

This won’t work either.

So what can you do? The solution is to use apis that the dom considers safe. It didn’t consider innerHTML safe, but there are other ways to add the script tag that it will accept. For example, you can use the createElement api. In React, this would go into a custom hook.

import { useEffect, useState } from "react";
export const useScript = (url) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const script = document.createElement("script");
script.src = url;
script.async = true;
script.onload = () => {
setLoading(false);
};
script.onerror = () => {
setError(`Failed to load script ${url}`);
setLoading(false);
};
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [url]);
return { loading, error };
};

Don’t get overwhelmed by the code, it is just adding a script tag to the document, and removing with when the component unmounts. For our use case, we can use this hook to add our google analytics script tag.

We can create a component that will house this our useScript hook.

const GoogleAnalyticsLoader = () => {
const { loading, error } = useScript("gpt.js");
return null;
};

and then we can use this component in our app.

const App = () => {
return (
<>
<GoogleAnalyticsLoader />
<body>
<h1>This is my web page!</h1>
</body>
</>
);
};
export default App;

Now our script tag will be added to the dom dynamically depending on if the user has given us consent to track them.

Note: If you are using modern frameworks like Next.js that ship with the canary version of React, they support rendering script tags dynamically in jsx without having to do anything! It is like magic (See Next.js docs)

Hope this was helpful!