I have a relatively straightforward issue of trying to add inline scripting to a React component. What I have so far:
\'use strict\';
import \'../../styles/
To add script tag or code in head tag <head>
, use react-helmet package. it is light and have good documentation.
To add Js code in script tag inside body,
function htmlDecode(html) {
return html.replace(/&([a-z]+);/ig, (match, entity) => {
const entities = { amp: '&', apos: '\'', gt: '>', lt: '<', nbsp: '\xa0', quot: '"' };
entity = entity.toLowerCase();
if (entities.hasOwnProperty(entity)) {
return entities[entity];
}
return match;
});
}
render() {
const scriptCode = `<script type="text/javascript">
{(function() {
window.hello={
FIRST_NAME: 'firstName',
LAST_NAME: 'lastName',
};
})()}
</script>`
return(
<div dangerouslySetInnerHTML={{ __html: this.htmlDecode(scriptCode) }} />;
);
}
this code can be tested by console.log(windows.hello)
You can find best answer at the following link:
https://cleverbeagle.com/blog/articles/tutorial-how-to-load-third-party-scripts-dynamically-in-javascript
const loadDynamicScript = (callback) => { const existingScript = document.getElementById('scriptId'); if (!existingScript) { const script = document.createElement('script'); script.src = 'url'; // URL for the third-party library being loaded. script.id = 'libraryName'; // e.g., googleMaps or stripe document.body.appendChild(script); script.onload = () => { if (callback) callback(); }; } if (existingScript && callback) callback(); };
A bit late to the party but I decided to create my own one after looking at @Alex Macmillan answers and that was by passing two extra parameters; the position in which to place the scripts such as or and setting up the async to true/false, here it is:
import { useEffect } from 'react';
const useScript = (url, position, async) => {
useEffect(() => {
const placement = document.querySelector(position);
const script = document.createElement('script');
script.src = url;
script.async = typeof async === 'undefined' ? true : async;
placement.appendChild(script);
return () => {
placement.removeChild(script);
};
}, [url]);
};
export default useScript;
The way to call it is exactly the same as shown in the accepted answer of this post but with two extra(again) parameters:
// First string is your URL
// Second string can be head or body
// Third parameter is true or false.
useScript("string", "string", bool);
Solution depends on scenario. Like in my case, I had to load a calendly embed inside a react component.
Calendly looks for a div and reads from it's data-url
attribute and loads an iframe inside the said div.
It is all good when you first load the page: first, div with data-url
is rendered. Then calendly script is added to body. Browser downloads and evaluates it and we all go home happy.
Problem comes when you navigate away and then come back into the page. This time the script is still in body and browser doesn't re-download & re-evaluate it.
componentWillUnmount
find and remove the script element. Then on re mount, repeat the above steps.$.getScript
. It is a nifty jquery helper that takes a script URI and a success callback. Once the script it loaded, it evaluates it and fires your success callback. All I have to do is in my componentDidMount
$.getScript(url)
. My render
method already has the calendly div. And it works smooth.Do you want to fetch and execute the script again and again, every time this component is rendered, or just once when this component is mounted into the DOM?
Perhaps try something like this:
componentDidMount () {
const script = document.createElement("script");
script.src = "https://use.typekit.net/foobar.js";
script.async = true;
document.body.appendChild(script);
}
However, this is only really helpful if the script you want to load isn't available as a module/package. First, I would always:
npm install typekit
)import
the package where I need it (import Typekit from 'typekit';
)This is likely how you installed the packages react
and react-document-title
from your example, and there is a Typekit package available on npm.
Now that we have hooks, a better approach might be to use useEffect
like so:
useEffect(() => {
const script = document.createElement('script');
script.src = "https://use.typekit.net/foobar.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, []);
Which makes it a great candidate for a custom hook (eg: hooks/useScript.js
):
import { useEffect } from 'react';
const useScript = url => {
useEffect(() => {
const script = document.createElement('script');
script.src = url;
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, [url]);
};
export default useScript;
Which can be used like so:
import useScript from 'hooks/useScript';
const MyComponent = props => {
useScript('https://use.typekit.net/foobar.js');
// rest of your component
}
There is a very nice workaround using Range.createContextualFragment
.
/**
* Like React's dangerouslySetInnerHTML, but also with JS evaluation.
* Usage:
* <div ref={setDangerousHtml.bind(null, html)}/>
*/
function setDangerousHtml(html, el) {
if(el === null) return;
const range = document.createRange();
range.selectNodeContents(el);
range.deleteContents();
el.appendChild(range.createContextualFragment(html));
}
This works for arbitrary HTML and also retains context information such as document.currentScript
.