I wonder how I would add an external script to an React application and make its functions available. Let's take this script https://www.cssscript.com/confetti-falling-animation/
In a "not-react-application" I would add it like this in my DOM
<script src="confetti.js"></script>
and then call it's functions like this
startConfetti();
or
stopConfetti();
However, this does not work in React. I know that I can add a <script /> tag like this:
useEffect(() => {
const script = document.createElement('script');
script.src = './confetti.js';
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, []);
But this does not make the functions startConfetti() or stopConfetti() available. They are undefined.
How would I add the script and its functionalities in a React App?
Add the script in index.html's head tag, like so (with the correct path to the JavaScript file):
<script src="confetti.js"></script>
Then in your React component, you could get it with the help of window object, this way:
const startConfetti = window.startConfetti;
startConfetti();
Related
I'm Using a external JS from an external enterprise. Is mandatory use this script online and not import it.
one of the complexity is that they've his own html tag to call a functions that are inside from a script tag. I've created this script from typescript side to control some variables, but i need to call a function to have a value needed in the end of the flux.
So, in short, I need call a TypeScript Function from this script tag created.
here some code:
let script = document.createElement('script');
script.type = `text/javascript`;
script.text = `
var success = function (data) {
let transactions= ${JSON.stringify(this.transactions)};
let internalId = this.getInternalId('Hello there!'); -----> The TS Function that I want to call
};`;
document.getElementsByTagName('body')[0].appendChild(script);
getInternalId(value:string):void {
console.log('a');
}
greetings!
UPDATE
I tried to abstract the function in the HTML tag like this:
[attr.data-order-id]="success"
and in the ts file like this:
success = (data:any ) => {
debugger;
alert(JSON.stringify(this.transactions;));
};
But I lost every reference of the proyect and somehow it function is executed from other file:
The function is executed from another file. On the left tab is the ts file where function is declarated.
This is somewhat similar to this question:
Adding script tag to React/JSX
But in my case I am loading a script like this:
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','ID');</script>
<!-- End Google Tag Manager -->
Now I know there is a npm package for the google tag manager but I am curious if I would like to do this in a custom way how would I go about?
In the above question I see a lot of:
const script = document.createElement("script");
script.src = "https://use.typekit.net/foobar.js";
script.async = true;
document.body.appendChild(script);
Which is fine but if I have a function inside of the loaded script how would I go about executing this correctly?
To add a random script like this, you could:
Add the script to your index.html
Paste the code to a file and use an import statement.
Dynamically load the script once the user does something, using code splitting.
1. Adding the script to your HTML
Just stick the script tags in your index.html file, preferably at the end of the body tags. If using create-react-app, the index.html file is located in the public directory:
<body>
<div id="root"></div>
<script>/* your script here */</script>
</body>
2. Import from file
Alternatively, you could paste the script into a .js file, and import it from anywhere in your code. A good place to import general scripts would be in your index.js entry point file. This approach has the benefit of including the script with the rest of your js bundle, enabling minification and tree shaking.
// index.js
import "../path/to/your/script-file";
3. Code splitting
Lastly, if you would like to dynamically load a piece of js code in a certain point in time, while making sure it isn't part of your starting bundle, you could do code splitting, using dynamic imports. https://create-react-app.dev/docs/code-splitting
function App() {
function handleLoadScript() {
import('./your-script')
.then(({ functionFromModule }) => {
// Use functionFromModule
})
.catch(err => {
// Handle failure
});
};
return <button onClick={handleLoadScript}>Load</button>;
}
Usually, one can update an HTML element in react using the dangerouslySetInnerHTML prop.
But for the case of a script that is to be executed, this won't work, as discussed in this other SO question.
An option you have to achieve this, is appending the element inside a new document context, using the document Range API, createContextualFragment
Working example below.
Note that I've tweaked your script a bit to show some ways to customize it.
const { useState, useRef, useEffect, memo } = React;
const MyCustomScriptComponent = () => {
const [includeScript, setIncludeScript] = useState(false)
// just some examples of customizing the literal script definition
const labelName = 'dataLayer'
const gtmId = 'ID' // your GTM id
// declare the custom <script> literal string
const scriptToInject = `
<script>
(function(w,d,s,l,i){
const gtmStart = new Date().getTime();
w[l]=w[l]||[];w[l].push({'gtm.start':
gtmStart,event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
console.log("loaded at gtmStart:", gtmStart);
})(window,document,'script','${labelName}','${gtmId}');
console.log("fetching GTM using id '${gtmId}'");
</script>`
const InjectScript = memo(({ script }) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current === null) {
return;
}
// create a contextual fragment that will execute the script
// beware of security concerns!!
const doc = document
.createRange()
.createContextualFragment(script)
// clear the div HTML, and append the doc fragment with the script
divRef.current.innerHTML = ''
divRef.current.appendChild(doc)
})
return <div ref={divRef} />
})
const toggleIncludeScript = () => setIncludeScript((include) => !include)
return (
<div>
{includeScript && <InjectScript script={scriptToInject} />}
<p>Custom script {includeScript ? 'loaded!' : 'not loaded.'}</p>
<button onClick={toggleIncludeScript}>Click to load</button>
</div>
)
}
ReactDOM.render(<MyCustomScriptComponent />, document.getElementById('app'))
Try it live on codepen.
For additional reference, you can find more alternatives to inject a script in this medium post.
So, imagine Vue index.html that also loads some custom script:
<!DOCTYPE html>
<html lang="en">
<head>
...
...
<script type="text/javascript">
languagePluginLoader.then(function () {
pyodide.loadPackage("someName").then(() => {
// Send message to Vue that everything is fine
}).catch((err) => {
// Send message to Vue that it failed
})
})
</script>
...
...
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Is there a way to communicate with running Vue instance or/and Vuex from the index.html file? For example, I want to show "Loading..." until the script is fully loaded, etc.
One way will be to send the message to the service worker and then from the service worker to Vue, but it feels unpractical.
Another way is to set windows.script_status = true after the initialization, but window object is not reactive, so Vue will check it once, get undefined and forget about it.
UPD: Third way will be to inject scripts from the Vue side and put some function into script.onload to get when it's ready, but not sure how stable the solution is.
So, any advice will do :)
I would go the route of an external event hub. Since Vue 3 removed the $on, $off and $once instance methods, the official migration strategy for an event hub is to use an external library, such as mitt. Using e.g. mitt you should be able to signal Vue easily once your other custom scripts have been loaded.
The solution was the third one - inject script manually through mounted and put all the logic into script.onload part. Google Maps example:
mounted: function () {
if (window.google && window.google.maps) {
this.create_map();
return;
}
var self = this;
var script = document.createElement("script");
script.onload = function () {
if (!window.google && !window.google.maps)
return void (console.error("no google maps script included"));
self.create_map();
};
script.async = true;
script.defer = true;
script.src = "https://maps.googleapis.com/maps/api/js?key=googleapikeyxxxx&callback=initMap";
document.getElementsByTagName("head")[0].appendChild(script);
}
Picked the logic from another SO question's answer: https://stackoverflow.com/a/45605316/1598470.
I am dynamically adding a <script> tag to the document <head> on page load based on the environment.
The Function:
export const loadScript = () => {
// load script tag into head
const HEAD = document.getElementsByTagName('head')
const SCRIPT_TAG = document.createElement('script')
SCRIPT_TAG.setAttribute('src', process.env.SCRIPT_SRC)
SCRIPT_TAG.setAttribute('async', true)
HEAD[0].append(SCRIPT_TAG)
}
I want to write a test that checks if once the loadScript() function is run that the <script> tag made it into the head. Our environment is set up with Jest, and I haven't found a satisfactory example that demonstrates how to do it, or works.
I am new to testing, and would appreciate any solutions, or hints offered.
I suppose the easiest way to test it would be something like this:
test('loadScript', () => {
process.env.SCRIPT_SRC = 'the-src';
loadScript();
expect(document.head.innerHTML).toBe('<script src="the-src" async="true"></script>');
});
This works because the default test environment for Jest is jsdom which simulates the document.
test('loadScript', () => {
loadScript();
const script = document.getElementsByTagName('script')[0];
// trigger the callback
script.onreadystatechange(); // or script.onLoad();
expect("something which you have on load").toBe('expected result on load');
});
I have a couple of react files which contain react components, e.g.:
class MyComponent extends React.Component {
...
}
Currently, I import them using the script elements, e.g.:
<script type="text/babel" src="http://127.0.0.1:3000/myComponent.js"> </script>
I want to dynamically load them from a single js file. Therefore I was following the idea presented here:
var script = document.createElement('script');
script.type='text/babel';
script.onload = function () {
console.log('done');
resolve();
};
script.src = "http://127.0.0.1:3000/myComponent.js";
document.head.appendChild(script);
However, onload is never called and I do not get any error message in Chrome. If I change type to "text/javascript" the onload method is called but I get a syntax error: "<" is a undefined token....
I know that I can compile the bable files to js but I dont want to that during development...
Try this alternate code, see if this works for you. Apparently you need babel script before text/babel can be identified as valid.
Look at In browser here: https://babeljs.io/en/setup#installation
var script = document.createElement("script");
script.type = 'text/babel';
script.addEventListener("load", function(event) {
console.log("Script finished loading and executing");
});
script.src = "https://code.jquery.com/jquery-3.3.1.min.js";
document.getElementsByTagName("script")[0].parentNode.appendChild(script);
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<div id="output"></div>
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>