Unable to access property in javascript file in react component - javascript

I am building a chrome extension, which uses react/webpack, and it needs to inject code into an existing page. So I need to load one of my javascript files when the component is rendered.
However, I cannot access properties in that javascript file. I get error 'Cannot read properties of undefined (reading 'initialvalue')'. I have given the manifest.json file all the proper permissions and I know the script file is loading correctly. What am I doing wrong?
plugin.bundle.js (javascript file I'm trying to load)
var plugin = plugin || {};
SetupPlugin();
function SetupPlugin() {
plugin.initialvalue = plugin.initialvalue || 123;
}
index.jsx
import { render } from 'react-dom';
import Embedtab from './Embedtab';
render(<Embedtab />, window.document.body.querySelector('.someclass'));
Embedtab.tsx
declare global {
interface Window {
plugin: any;
}
}
export class Embedtab extends React.Component<any> {
componentDidMount() {
const script = document.createElement("script");
script.src = chrome.runtime.getURL("./../plugin.bundle.js");
script.async = false;
script.onload = () => this.scriptLoaded();
document.body.appendChild(script);
}
scriptLoaded() {
alert("embedded loaded, value is " + window.plugin.initialvalue); //errors here
}
render() {
return (
<div className="App">
<header className="App-header">
Hello World
</header>
</div>
);
}
};
export default Embedtab;

Related

How to unmount next/script on page change in next.js?

I want to load a script only on certain pages. Next.js recommends using next/script tag for it.
However, when I navigate to some different pages I can still see the script present at the end of body in HTML.
import Script from "next/script";
const Comments = () => {
return (
<div className="giscus mt-16">
<Script
src="https://giscus.app/client.js"
data-repo="GorvGoyl/Personal-Site-Gourav.io"
data-repo-id="MDEwOlJlcG9zaXRvcnkyOTAyNjQ4MTU="
data-category="Announcements"
data-category-id="DIC_kwDOEU0W784CAvcn"
data-mapping="pathname"
data-reactions-enabled="0"
data-emit-metadata="0"
data-theme="light"
data-lang="en"
crossOrigin="anonymous"
strategy="lazyOnload"
onError={(e) => {
console.error("giscus script failed to load", e);
}}
></Script>
</div>
);
};
I suspect Next.js is not cleaning up the script on route change action. How do I make sure that scripts get removed on page change?
I was facing the same problem. Only solution that works for me is to reload the page as the routeChangeStart. I also made it in a custom Hook so can be reused with ease.
Custom Hook
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function useScript(url) {
const router = useRouter();
useEffect(() => {
const script = document.createElement("script");
script.src = url;
script.async = true;
document.body.appendChild(script);
// Needed for cleaning residue left by the external script that can only be removed by reloading the page
const onRouterChange = () => {
router.reload();
};
router.events.on("routeChangeStart", onRouterChange);
return () => {
router.events.off("routeChangeStart", onRouterChange);
document.body.removeChild(script);
};
}, [router]);
}
Usage:
import useScript from "../utils/useScript"
export default function index(){
useScript("<script-ulr>");
}
I did a lot of crawling on the internet and tried a lot of different methods from forums because this seems to be an issue with next.js but the one that worked for me with the least amount of code was adding a script tag with javascript in the useEffect hook. I tried it randomly and I didn't think it would work to unmount it on it's own but it did without any extras. I have the brackets empty so it only runs on mount.
export default function Page() {
useEffect(() => {
const contentDiv = document.getElementById("content");
const newScript = document.createElement("script");
newScript.src = "url";
newScript.id = "script-id";
newScript.async = true;
// add rest of your script items
contentDiv?.appendChild(newScript);
}, [])
return (
<div className='content'>
</div>
)
}

External script with classes in React

I am trying to embed an external map service into my React app. Their recommendation to integrate the API into a regular HTML web page looks like this:
<script type="text/javascript" src="//map.search.ch/api/map.js"></script>
<script type="text/javascript">new SearchChMap({center:[123456,987654],y:"-1m",poigroups:"default"});</script>
I tried to apply this in my typescript React component as follows:
declare class SearchChMap {
constructor(_: {})
}
// ...
<>
<script type="text/javascript" src="//map.search.ch/api/map.js"></script>
<script type="text/javascript">{new SearchChMap({ center: [123456, 987654], poigroups: "default" })}</script>
{/* ... */}
</>
While this compiles, I receive the following runtime error: ReferenceError: SearchChMap is not defined.
What is the correct way of using legacy JavaScript classes from an externally hosted script in your React components?
Update:
I tried this answer to no avail, using the following code:
componentDidMount(): void {
const script1: HTMLScriptElement = document.createElement("script");
script1.src = "//map.search.ch/api/map.js";
script1.async = true;
document.body.appendChild(script1);
const script2: HTMLScriptElement = document.createElement("script");
script2.text = 'new SearchChMap({center:[123456,987654],y:"-1m",poigroups:"default"});';
script2.async = true;
document.body.appendChild(script2);
}
This results in the exact same error message, just a few seconds later, since the scripts are added dynamically.
Update 2:
Moving the object creation to onload as suggested by AWolf did the trick:
componentDidMount(): void {
const scriptTag: HTMLScriptElement = document.createElement("script");
scriptTag.src = "//map.search.ch/api/map.js";
document.head.appendChild(scriptTag);
scriptTag.onload = () => {
new SearchChMap({ center: this.props.center, container: this.props.containerId });
};
}
You could add the script tag to public/index.html and use it with window.SearchChMap or if you prefer to load it from your componentDidMount you can do it like in the snippet below (same code as in the following sandbox).
It is using onload of the appended script tag to delay the object creation until the new script is loaded.
The useRef is used so you can use the created object in other locations of your function (not used in the example).
The useEffect in the code is required to ensure that the DOM is ready before adding the tag. Same behavior as in class-based components with componentDidMount lifecycle method.
The return of the useEffect could be used to do cleanup e.g. return () => { /* remove script tag & dispose the window.SearchChMap */} (same as componentWillUnmount)
import React, { useEffect, useRef } from "react";
export default () => {
const SearchMap = useRef(); // optional, but useful if the Map object is used after mounting
useEffect(() => {
const scriptTag = document.createElement("script");
scriptTag.src = "//map.search.ch/api/map.js";
document.body.appendChild(scriptTag);
scriptTag.onload = () => {
SearchMap.current = new window.SearchChMap({ center: "Zürich" });
};
}, [SearchMap]);
return (
<div
id="mapcontainer"
style={{ maxWidth: "500px", height: "400px", border: "2px inset #ccc" }}
/>
);
};

How to load the Google Places JS API Library in a React Project?

I am using the 'react-places-autocomplete' library. I understand that I have to load the API using my key. I can't figure out where to place the script for the key such that the program will work.
I saw a StackOverflow page where someone said to load it statically in index.js, which I tried:
import 'react-places-autocomplete';
...
ReactDOM.render(
<div>
<BrowserRouter>
<App />
</BrowserRouter>
<script src="https://maps.googleapis.com/maps/api/js?
key=MY_KEY&libraries=places"></script>
</div>
, document.getElementById('root'));
This doesn't work, I also tried to load it directly in the component (Which doesn't seem correct):
class My_Component extends React.Component {
...
render() {
return (
<div>
<script src="https://maps.googleapis.com/maps/api/js?
key=MY_KEY&libraries=places"></script>
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
....
</div>
);
}
}
Using these approaches I keep getting the "Google Maps JavaScript API library must be loaded" error, and I have looked at the documentation and it doesn't specify where the tag needs to be placed, just that it needs to be somewhere.
I have used it this way in one of my project
class PlacesAutocomplete1 extends React.Component {
constructor(props) {
super(props);
this.state = {
googleMapsReady: false,
};
}
componentDidMount() {
script is loaded here and state is set to true after loading
this.loadGoogleMaps(() => {
// Work to do after the library loads.
this.setState({ googleMapsReady: true });
});
}
componentWillUnmount() {
// unload script when needed to avoid multiple google scripts loaded warning
this.unloadGoogleMaps();
}
loadGoogleMaps = callback => {
const existingScript = document.getElementById("googlePlacesScript");
if (!existingScript) {
const script = document.createElement("script");
script.src =
"https://maps.googleapis.com/maps/api/js?key=YOUR_KEY&libraries=places";
script.id = "googleMaps";
document.body.appendChild(script);
//action to do after a script is loaded in our case setState
script.onload = () => {
if (callback) callback();
};
}
if (existingScript && callback) callback();
};
unloadGoogleMaps = () => {
let googlePlacesScript = document.getElementById("googlePlacesScript");
if (googlePlacesScript) {
googlePlacesScript.remove();
}
};
render() {
if (!this.state.googleMapsReady) {
return <p>Loading</p>;
}
return (
// do something you needed when script is loaded
}

How to include a javascript file in a React component to be executed upon page load? [duplicate]

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/pages/people.scss';
import React, { Component } from 'react';
import DocumentTitle from 'react-document-title';
import { prefix } from '../../core/util';
export default class extends Component {
render() {
return (
<DocumentTitle title="People">
<article className={[prefix('people'), prefix('people', 'index')].join(' ')}>
<h1 className="tk-brandon-grotesque">People</h1>
<script src="https://use.typekit.net/foobar.js"></script>
<script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}></script>
</article>
</DocumentTitle>
);
}
};
I have also tried:
<script src="https://use.typekit.net/foobar.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>
Neither approach seems to execute the desired script. I'm guessing it's a simple thing I'm missing. Can anybody help out?
PS: Ignore the foobar, I have a real id actually in use that I didn't feel like sharing.
Edit: Things change fast and this is outdated - see update
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:
Look for the package on npm
Download and install the package in my project (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.
Update:
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
}
My favorite way is to use React Helmet – it's a component that allows for easy manipulation of the document head in a way you're probably already used to.
e.g.
import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<script src="https://use.typekit.net/foobar.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>
</Helmet>
...
</div>
);
}
};
https://github.com/nfl/react-helmet
Further to the answers above you can do this:
import React from 'react';
export default class Test extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.innerHTML = "document.write('This is output by document.write()!')";
this.instance.appendChild(s);
}
render() {
return <div ref={el => (this.instance = el)} />;
}
}
The div is bound to this and the script is injected into it.
Demo can be found on codesandbox.io
This answer explains the why behind this behavior.
Any approach to render the script tag doesn't work as expected:
Using the script tag for external scripts
Using dangerouslySetInnerHTML
Why
React DOM (the renderer for react on web) uses createElement calls to render JSX into DOM elements.
createElement uses the innerHTML DOM API to finally add these to the DOM (see code in React source). innerHTML does not execute script tag added as a security consideration. And this is the reason why in turn rendering script tags in React doesn't work as expected.
For how to use script tags in React check some other answers on this page.
If you need to have <script> block in SSR (server-side rendering), an approach with componentDidMount will not work.
You can use react-safe library instead.
The code in React will be:
import Safe from "react-safe"
// in render
<Safe.script src="https://use.typekit.net/foobar.js"></Safe.script>
<Safe.script>{
`try{Typekit.load({ async: true });}catch(e){}`
}
</Safe.script>
The answer Alex Mcmillan provided helped me the most but didn't quite work for a more complex script tag.
I slightly tweaked his answer to come up with a solution for a long tag with various functions that was additionally already setting "src".
(For my use case the script needed to live in head which is reflected here as well):
componentWillMount () {
const script = document.createElement("script");
const scriptText = document.createTextNode("complex script with functions i.e. everything that would go inside the script tags");
script.appendChild(scriptText);
document.head.appendChild(script);
}
I tried to edit the accepted answer by #Alex McMillan but it won't let me so heres a separate answer where your able to get the value of the library your loading in. A very important distinction that people asked for and I needed for my implementation with stripe.js.
useScript.js
import { useState, useEffect } from 'react'
export const useScript = (url, name) => {
const [lib, setLib] = useState({})
useEffect(() => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onload = () => setLib({ [name]: window[name] })
document.body.appendChild(script)
return () => {
document.body.removeChild(script)
}
}, [url])
return lib
}
usage looks like
const PaymentCard = (props) => {
const { Stripe } = useScript('https://js.stripe.com/v2/', 'Stripe')
}
NOTE: Saving the library inside an object because often times the library is a function and React will execute the function when storing in state to check for changes -- which will break libs (like Stripe) that expect to be called with specific args -- so we store that in an object to hide that from React and protect library functions from being called.
You can also use react helmet
import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://example.com/example" />
<script src="/path/to/resource.js" type="text/javascript" />
</Helmet>
...
</div>
);
}
};
Helmet takes plain HTML tags and outputs plain HTML tags. It's dead simple, and React beginner friendly.
I created a React component for this specific case: https://github.com/coreyleelarson/react-typekit
Just need to pass in your Typekit Kit ID as a prop and you're good to go.
import React from 'react';
import Typekit from 'react-typekit';
const HtmlLayout = () => (
<html>
<body>
<h1>My Example React Component</h1>
<Typekit kitId="abc123" />
</body>
</html>
);
export default HtmlLayout;
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.
Here is how I was finally able to add two external JavaScript files in my React JS code:
These are the steps I followed.
Step 1:
I installed React-Helmet using npm i react-helmet from the terminal while inside my react-app folder path.
Step 2:
I then added import {Helmet} from "react-helmet"; header in my code.
Step 3:
Finally, in my code this is
how I added the external JS files using Helment
<Helmet>
<script src = "path/to/my/js/file1.js" type = "text/javascript" />
<script src = "path/to/my/js/file2.js" type = "text/javascript" />
</Helmet>
You can use npm postscribe to load script in react component
postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
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();
};
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)
Very similar to other answers just using default values to clean up undefined checks
import { useEffect } from 'react'
const useScript = (url, selector = 'body', async = true) => {
useEffect(() => {
const element = document.querySelector(selector)
const script = document.createElement('script')
script.src = url
script.async = async
element.appendChild(script)
return () => {
element.removeChild(script)
}
}, [url])
}
export default useScript
usage
useScript('/path/to/local/script.js') // async on body
useScript('https://path/to/remote/script.js', 'html') // async on html
useScript('/path/to/local/script.js', 'html', false) // not async on html.. e.g. this will block
According to Alex McMillan's solution, I have the following adaptation.
My own environment: React 16.8+, next v9+
// add a custom component named Script
// hooks/Script.js
import { useEffect } from 'react'
// react-helmet don't guarantee the scripts execution order
export default function Script(props) {
// Ruels: alwasy use effect at the top level and from React Functions
useEffect(() => {
const script = document.createElement('script')
// src, async, onload
Object.assign(script, props)
let { parent='body' } = props
let parentNode = document.querySelector(parent)
parentNode.appendChild(script)
return () => {
parentNode.removeChild(script)
}
} )
return null // Return null is necessary for the moment.
}
// Use the custom compoennt, just import it and substitute the old lower case <script> tag with the custom camel case <Script> tag would suffice.
// index.js
import Script from "../hooks/Script";
<Fragment>
{/* Google Map */}
<div ref={el => this.el = el} className="gmap"></div>
{/* Old html script */}
{/*<script type="text/javascript" src="http://maps.google.com/maps/api/js"></script>*/}
{/* new custom Script component */}
<Script async={false} type="text/javascript" src='http://maps.google.com/maps/api/js' />
</Fragment>
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);
I recently faced the issue,
Tried multiple solutions given here, at last sattled with iframe,
Iframe seems to work seamlessly if it you are trying to integrate a js plugin on a specific screen
<iframe
id="xxx"
title="xxx"
width="xxx"
height="xxx"
frameBorder="value"
allowTransparency
srcDoc={`
<!doctype html>
<html>
<head>
<title>Chat bot</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body style="width:100%">
<script type="text/javascript">
......
</script>
</body>
</html>
`}
/>
componentDidMount() {
const head = document.querySelector("head");
const script = document.createElement("script");
script.setAttribute(
"src",
"https://assets.calendly.com/assets/external/widget.js"
);
head.appendChild(script);
}
just add in body in html file
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
Honestly, for React - don't bother with messing around adding <script> tags to your header. It's a pain in the ass to get a callback when they have loaded fully. Instead, use a package like #charlietango/useScript to load the script when you need it and get a status update when it is completed.
Example usage:
import React from 'react'
import useScript, { ScriptStatus } from '#charlietango/use-script'
const Component = () => {
const [ready, status] = useScript('https://api.google.com/api.js')
if (status === ScriptStatus.ERROR) {
return <div>Failed to load Google API</div>
}
return <div>Google API Ready: {ready}</div>
}
export default Component
PS. If you're using redux to tell other components when your script has loaded, and are using redux-persist like I was, don't forget to include a modifier on your redux-persist setup that always sets the script loaded redux value to false in the redux backup.
for multiple scripts, use this
var loadScript = function(src) {
var tag = document.createElement('script');
tag.async = false;
tag.src = src;
document.getElementsByTagName('body').appendChild(tag);
}
loadScript('//cdnjs.com/some/library.js')
loadScript('//cdnjs.com/some/other/library.js')
For a more complete useScript implementation that supports loading status and error handling, check out this from useHooks.
Usage
function App() {
const status = useScript(
"https://pm28k14qlj.codesandbox.io/test-external-script.js"
);
return (
<div>
<div>
Script status: <b>{status}</b>
</div>
{status === "ready" && (
<div>
Script function call response: <b>{TEST_SCRIPT.start()}</b>
</div>
)}
</div>
);
}
Hook
function useScript(src) {
// Keep track of script status ("idle", "loading", "ready", "error")
const [status, setStatus] = useState(src ? "loading" : "idle");
useEffect(
() => {
// Allow falsy src value if waiting on other data needed for
// constructing the script URL passed to this hook.
if (!src) {
setStatus("idle");
return;
}
// Fetch existing script element by src
// It may have been added by another intance of this hook
let script = document.querySelector(`script[src="${src}"]`);
if (!script) {
// Create script
script = document.createElement("script");
script.src = src;
script.async = true;
script.setAttribute("data-status", "loading");
// Add script to document body
document.body.appendChild(script);
// Store status in attribute on script
// This can be read by other instances of this hook
const setAttributeFromEvent = (event) => {
script.setAttribute(
"data-status",
event.type === "load" ? "ready" : "error"
);
};
script.addEventListener("load", setAttributeFromEvent);
script.addEventListener("error", setAttributeFromEvent);
} else {
// Grab existing script status from attribute and set to state.
setStatus(script.getAttribute("data-status"));
}
// Script event handler to update status in state
// Note: Even if the script already exists we still need to add
// event handlers to update the state for *this* hook instance.
const setStateFromEvent = (event) => {
setStatus(event.type === "load" ? "ready" : "error");
};
// Add event listeners
script.addEventListener("load", setStateFromEvent);
script.addEventListener("error", setStateFromEvent);
// Remove event listeners on cleanup
return () => {
if (script) {
script.removeEventListener("load", setStateFromEvent);
script.removeEventListener("error", setStateFromEvent);
}
};
},
[src] // Only re-run effect if script src changes
);
return status;
}
I had raw html string with javascript/Jquery
i installed npm library dangerously-set-html-content
npm i dangerously-set-html-content
import InnerHTML from 'dangerously-set-html-content'
<div>
<InnerHTML html={html}/>
</div>
or
import InnerHTML from 'dangerously-set-html-content'
const renderhtml=`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title> is not defined</title>$(document).ready(function(){ $("button").click(function(){ alert("jQuery is working perfectly."); }); });</script></head><body> <button type="button">Test jQuery Code</button></body></html>`
<div>
<InnerHTML html={renderhtml}/>
</div>
Make sure you add jquery cdn to public/index.html file
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous" async="true" ></script>
You could try to use the following:
Make sure you trust the script
<script>{`
function myFunction(index, row) {
return index;
}
`}
</script>
You have to create a component for this script, you call this component as a standard ES6 script tag
'use strict';
import '../../styles/pages/people.scss';
import React, { Component } from 'react';
import DocumentTitle from 'react-document-title';
import { prefix } from '../../core/util';
export default class extends Component {
render() {
return (
<DocumentTitle title="People">
<article className={[prefix('people'), prefix('people', 'index')].join(' ')}>
<h1 className="tk-brandon-grotesque">People</h1>
</article>
</DocumentTitle>
);
class Component extend Index.App {
<script src="https://use.typekit.net/foobar.js" />
<script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}/>
}
}
};
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.
Fix:
On componentWillUnmount find and remove the script element. Then on re mount, repeat the above steps.
Enter $.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.
I saw the same problem, until I found this package, quite easy to implement, I hope it works as it worked for me :)
https://github.com/gumgum/react-script-tag
import React from 'react';
import Script from '#gumgum/react-script-tag';
import './App.css';
function App() {
return (
<div >
<h1> Graphs</h1>
<div class="flourish-embed flourish-network" data-src="visualisation/8262420">
<Script src"your script"
</Script>
</div>
</div>
);
}
export default App;
You can put your script in an Html file before react is being called.

Dynamically load a stylesheet with React

I'm building a CMS system for managing marketing landing pages. On the "Edit Landing Page" view, I want to be able to load the associated stylesheet for whichever landing page the user is editing. How could I do something like this with React?
My app is fully React, isomorphic, running on Koa. My basic component heirarchy for the page in question looks something like this:
App.jsx (has `<head>` tag)
└── Layout.jsx (dictates page structure, sidebars, etc.)
└── EditLandingPage.jsx (shows the landing page in edit mode)
Data for the landing page (including the path of the stylesheet to load) is fetched asynchronously in EditLandingPage in ComponentDidMount.
Let me know if you need any additional info. Would love to get this figured out!
Bonus: I'd also like to unload the stylesheet when navigating away from the page, which I assume I can do the reverse of whatever answer comes my way in ComponentWillUnmount, right?
Just update stylesheet's path that you want to be dynamically loaded by using react's state.
import * as React from 'react';
export default class MainPage extends React.Component{
constructor(props){
super(props);
this.state = {stylePath: 'style1.css'};
}
handleButtonClick(){
this.setState({stylePath: 'style2.css'});
}
render(){
return (
<div>
<link rel="stylesheet" type="text/css" href={this.state.stylePath} />
<button type="button" onClick={this.handleButtonClick.bind(this)}>Click to update stylesheet</button>
</div>
)
}
};
Also, I have implemented it as react component. You can install via npm install react-dynamic-style-loader.
Check my github repository to examine: https://github.com/burakhanalkan/react-dynamic-style-loader
I think that Burakhan answer is correct but it is weird to load <Link href = "" /> inside the body tag. That's why I think it should be modified to the following [ I use React hooks]:
import * as React from 'react';
export default MainPage = (props) => {
const [ stylePath, setStylePath ] = useState("style1.css");
const handleButtonClick = () => {
setStylePath({stylePath: 'style2.css'});
}
useEffect(() => {
var head = document.head;
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = stylePath;
head.appendChild(link);
return () => { head.removeChild(link); }
}, [stylePath]);
return (
<div>
<button type="button" onClick={handleButtonClick}>
Click to update stylesheet
</button>
</div>
);
};
This is prime mixin teritority. First we'll define a helper to manage style sheets.
We need a function that loads a style sheet, and returns a promise for its success. Style sheets are actually pretty insane to detect load on...
function loadStyleSheet(url){
var sheet = document.createElement('link');
sheet.rel = 'stylesheet';
sheet.href = url;
sheet.type = 'text/css';
document.head.appendChild(sheet);
var _timer;
// TODO: handle failure
return new Promise(function(resolve){
sheet.onload = resolve;
sheet.addEventListener('load', resolve);
sheet.onreadystatechange = function(){
if (sheet.readyState === 'loaded' || sheet.readyState === 'complete') {
resolve();
}
};
_timer = setInterval(function(){
try {
for (var i=0; i<document.styleSheets.length; i++) {
if (document.styleSheets[i].href === sheet.href) resolve();
} catch(e) { /* the stylesheet wasn't loaded */ }
}
}, 250);
})
.then(function(){ clearInterval(_timer); return link; });
}
Well $#!#... I was expecting to just stick an onload on it, but nope. This is untested, so please update it if there are any bugs – it's compiled from several blog articles.
The rest is fairly straight forward:
allow loading a stylesheet
update state when it's available (to prevent FOUC)
unload any loaded stylesheets when the component unmounts
handle all the async goodness
var mixin = {
componentWillMount: function(){
this._stylesheetPromises = [];
},
loadStyleSheet: function(name, url){
this._stylesheetPromises.push(loadStyleSheet(url))
.then(function(link){
var update = {};
update[name] = true;
this.setState(update);
}.bind(this));
},
componentWillUnmount: function(){
this._stylesheetPromises.forEach(function(p){
// we use the promises because unmount before the download finishes is possible
p.then(function(link){
// guard against it being otherwise removed
if (link.parentNode) link.parentNode.removeChild(link);
});
});
}
};
Again, untested, please update this if there are any issues.
Now we have the component.
React.createClass({
getInitialState: function(){
return {foo: false};
},
componentDidMount: function(){
this.loadStyleSheet('foo', '/css/views/foo.css');
},
render: function(){
if (!this.state.foo) {
return <div />
}
// return conent that depends on styles
}
});
The only remaining todo is checking if the style sheet already exists before trying to load it. Hopefully this at least gets you on the right path.
I use react-helmet, in render function....
{inject ?
<Helmet>
<link rel="stylesheet" href="css/style.css" />
</Helmet> : null}
This is how I add style dynamically:
import React, { Component } from "react";
class MyComponent extends Component {
componentDidMount() {
const cssUrl = "/public/assets/css/style.css";
this.addStyle(cssUrl);
}
addStyle = url => {
const style = document.createElement("link");
style.href = url;
style.rel = "stylesheet";
style.async = true;
document.head.appendChild(style);
};
render() {
return <div> textInComponent </div>;
}
}
export default MyComponent;
On my approach i use this:
const TenantSelector = ({ children }) => {
// imagine its value from a json config
const config = {
custom_style: 'css/tenant.css'
}
require(`./assets/${config.custom_style}`)
return (
<>
<React.Suspense fallback={<></>}>
</React.Suspense>
{children}
</>
)
}
ReactDOM.render(
<TenantSelector>
<YourApp>
</TenantSelector>,
document.getElementById("root")
)
Instead of creating elements for stylesheet, you can also try importing your css based on some condition. ECMAScript provides a proposal that enables dynamic module imports, that works as follows:
if (condition) {
import('your css path here').then((condition) => {});
}

Categories