How to properly run a third party script in react? - javascript

I have a need in a React application to add a support chat made in bitrix - this is a script that is usually added to the index.html file.
example script:
<script>
(function(w,d,u){
var s=d.createElement('script');s.async=true;s.src=u+'?'+(Date.now()/60000|0);
var h=d.getElementsByTagName('script')[0];h.parentNode.insertBefore(s,h);
})(window,document,'https://someurl.js');
</script>
At the moment, I have implemented the connection as follows:
created a component with a script
import React, { Component } from 'react'
export default class Script extends Component {
componentDidMount() {
const s = document.createElement('script')
// s.type = 'text/javascript'
s.async = true
s.innerHTML = "document.write('This is output by document.write()!')"
s.src =
'https://someurl.js' +
'?' +
((Date.now() / 60000) | 0)
this.instance.appendChild(s)
const h = document.getElementsByTagName('script')[0]
h.parentNode.insertBefore(s, h)
}
render() {
return <div ref={(el) => (this.instance = el)} />
}
}
and connected it at the root of the application
import Script from './Script '
const App = () => {
return(
<>
<App />
<Script />
</>
)
}
It works, but I think this solution is not the most correct one. If Someone can help to run external script correctly in reactive applications (react, vue) I would be very grateful.
thanks in advance!!

Putting it in index.html is fine and the most compliant approach.
The only reasons to not do that would be:
Embedding the script is conditional - it depends on something you only know as soon as the React/Vue/etc. app is running. Or you want to delay the embedding.
You're hooking something like a Promise into the loading of the script, for example checking when the Bitrix chat is up and running
You need to pass values only known to the React/Vue/etc. app to the embedding of the script (api key for example)
Since none of these seem to be the case here, I'd keep it in index.html.

I remember there was a property called dangerousSetInnerHTML that you could look up on the React official website.

Related

A React, Vue, or other code depends on an object, not yet available. The best ways to wait for that variable, before rendering component content?

When an HTML document defined a variable that is not available until a later time during the page load.
Issue: A React, Vue, or other block of code depends on an object that has not yet been declared and outside the direct scope of the component, like window['varname']. What are the proper way(s) to wait for that variable to be defined before rendering a component's real content.
My Attempt:
import React from 'react'
import ReactDOM from 'react-dom/client'
import AppWrapper from "./components/AppWrapper";
const App = () => {
let intervalId
intervalId = setInterval(() => {
console.log('Waking up... checking if window.app is defined')
if (window['app'] !== undefined) {
console.log('yes')
clearInterval(intervalId)
} else {
console.log('no')
}
}, 1000)
if(app.ins.length === 0) {
return 'Loading...'
}
return (
<AppWrapper app={window['app']}></AppWrapper>
)
}
export default App
What other ways could you, should you, do it?
I will make it clearer for you :) I will describe exactly my problem: So I am writing a custom Joomla Component for Joomla. The Joomla Component is written in PHP and uses the Joomla Framework. Inside this component, I have written a Reactjs component. The way that you inject JavaScript into Joomla is via Joomla Methods. These methods either load the JS on the head of the document or in the body. Now, my Reactjs component is loaded during this process. This is fine and it works as long as I do not need to rely on outside variables.
I am using Joomla to store data that is need by the Reactjs component. The way that Joomla makes data available to JS is by a Joomla library that will inject the JS object into a script tag. This is also okay. The issue is that when the head tag loads the Reactjs component before the injected JS object, needed by the Reactjs component, is available. In my example above I store the global JS object into the window object as window.app = Some Object. Because the window.app object is not available at the time the Reactjs component has been loaded, I add a setInterval and check every 500 ms.
Then the setInterval wakes up and checks to see if the window["app"] is available yet. It keeps doing that until it is available. Once it is, it quits the interval and loads the Reactjs component container, passing in the required object.
Now, two things here:
I have no way of synchronizing this process in Joomla. Joomla is stubborn like that.
This is my attempted to only load the Reactjs container component once the data is available.
Question: Knowing the situation, what are the best strategies to accomplish this, apart from my current strategy?
Thanks :)
I believe, one of the approaches could be any kind of callback or subscription.
For example, you can define a function, which changes a state in state-container like redux.
(Pseudocode)
async function loadAppData(store) {
const data = await fetch('/some-data');
const json = await data.json();
store.dispatch('data-loaded', json)
}
And in component
function App() {
const appData = useSelector(store => store.appData);
if (!appData) {
return 'Loading...'
}
return <Markup />
}
Other option can be subscription. Again you can add some function which emits some event:
async function loadAppData(store) {
const data = await fetch('/some-data');
const json = await data.json();
eventBus.emit('data-loaded', json)
// or
window.appData = json
}
In react you can
function App() {
const [appData, setAppData] = useState();
useEffect(() => {
setAppData(window.appData)
}, [window.appData])
if (!appData) {
return 'Loading...'
}
return <Markup />
}
Or in Vue.js you could
data() {
return {
appData: ''
}
}
mounted() {
this.$on('data-loaded', this.onDataLoaded)
}
methods: {
onDataLoaded($event) {
this.appData = $event;
}
}

adding a script on Gatsby with setPostBodyComponents only on certain pages

Is it possible to render a script that has been inserted within the body tag (using setPostBodyComponents) only in certain pages other than all of them ?
Any ideas if this would be possible ?
As you can see in gatsby-ssr-js docs, onRenderBody exposes a bunch of props where there's a pathname.
pathname {string}
The pathname of the page currently being rendered.
That said, you can try something like:
const React = require("react")
exports.onRenderBody = ({ setPostBodyComponents, pathname }) => {
const SCRIPT_PAGES = ['/page-1', '/page-2'];
if(SCRIPT_PAGES.includes(pathname)){
setPostBodyComponents([
<script dangerouslySetInnerHTML={{ __html:`your script here`}} />,
]);
}
};
In that case, your SCRIPT_PAGES will hold the pages where the script will be inserted. Using that approach, you can tweak it as you want.

NextJS - ReferrenceError: document is not defined [duplicate]

This question already has answers here:
Window is not defined in Next.js React app
(23 answers)
Closed 1 year ago.
I'm running NextJS v11.1.12, and am trying to run a standard Vanilla JS function that toggles the CSS class of the div with the class "state-select-dropdown-box". My code is:
const Header = () => {
const dropDownMenu = document.querySelector('state-select-dropdown-box');
console.log(dropDownMenu);
function toggleMenu() {
dropDownMenu.classList.toggle('show');
}
return ( <Component /> );
}
When the app compiles, I get "Referrence Error: document is not defined". What's weird is I have got these functions running earlier today with no issues whatsoever and not changing a thing.
After some mass Googling and research, I've come to the conclusion that maybe I'm just not understanding how SSR works in NextJS? Could anyone explain or shine some light on why I'm not achieving the results that I'm expecting and how this can be solved?
You are getting this error because document object is not available on the server. Similarly, if you try to use window, localStorage then also it will throw the error.
These are only available on client-side(browser).
NextJs serves your pages from the server. So in your case it is trying to get document on the server, but document object is not available on the server. Hence you are getting the error.
To get rid of this error:
You need to specify that such code should be run on browser side rather than on the server.
So for this, you can use typeof document
So your code should be like this:
const dropDownMenu = typeof document !== 'undefined' && document.querySelector('state-select-dropdown-box');
Ref: https://github.com/vercel/next.js/issues/5354#issuecomment-520305040
#Surjeet Bhadauriya answer is technically correct, however next.js provide built in functionality to dynamically load components, in fact with next/dynamic you can load a component withoutSSR.
From the docs :
You may not always want to include a module on server-side. For
example, when the module includes a library that only works in the
browser.
const Header = () => {
const dropDownMenu = document.querySelector('state-select-dropdown-box');
console.log(dropDownMenu);
function toggleMenu() {
dropDownMenu.classList.toggle('show');
}
return ( <Component /> );
}
export default Header
Then in your page (or wherever you import it) :
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/Header'),
{ ssr: false } // <-- not including this component on server-side
)

Create BuyMeACoffee Component for Gatsby JS

I am trying to create a Buy Me A Coffee React component for my Gatsby website and even though my Gatsby site runs in development mode and successfully builds, the component (Buy Me A Coffee widget) doesn't show up when loading the page.
My website uses MDX, so ideally I'd like to be able to import the component into my blog posts. I like the idea of importing it into my blog posts because it allows me to optionally include it, whereas if I use a standard solution like gatsby-ssr.js for including the third party Buy Me A Coffee script, I foresee it being much more difficult to regulate on what pages the component does and doesn't show.
At the moment, I use the library browser-monads so I don't have to do typeof !== "undefined" conditional check for building my site. Using the traditional conditional format they recommend here doesn't help. Also styles.scss is currently empty. I am importing this in case I need to go back and add styles to my component later.
Thanks for your help!
Below is my code:
import React from 'react';
import './styles.scss'
import { window, document, exists } from 'browser-monads';
class BuyMeACoffee extends React.Component {
constructor(props){
super(props)
let script = document.createElement("script");
script.setAttribute('data-name','BMC-Widget')
script.src = "https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js"
script.setAttribute('data-id', 'x');
script.setAttribute('data-description', 'Thank you for your support!');
script.setAttribute('data-message', "We're proudly reader-supported! If our content helps you, we would be honored and greatly appreciate it if you'd consider buying us a coffee!");
script.setAttribute('data-color',"#2962ff")
script.setAttribute('data-position','right')
script.setAttribute('data-x_margin','18')
script.setAttribute('data-y-margin','18')
script.async = true
//Call window on load to show the image
script.onload=function(){
var evt = document.createEvent('Event');
evt.initEvent('load', false, false);
window.dispatchEvent(evt);
}
this.script=script
}
componentDidMount () {
document.head.appendChild(this.script)
}
componentWillUnmount(){
document.head.removeChild(this.script);
document.body.removeChild(document.getElementById("bmc-wbtn"))
}
render(){
return(null)
}
}
export default LoadBuyMeACoffee;
I would suggest using gatsby-ssr.js approach instead of adding a load performance directly in the class component. Something like this should work for you:
const React = require("react");
exports.onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script
data-name="Mac-Mann-Widget"
src="https://cdn.buymeacoffee.com/widget/1.0.0/prod/widget.prod.min.js"
data-id="eshlox"
data-description="Support me on Buy me a coffee!"
data-message="Thank you for visiting. You can now buy me a coffee!"
data-color="#FF813F"
data-position="right"
data-x_margin="28"
data-y_margin="18"
></script>,
]);
};
I will leave the snippet above to see if it helps someone.
Thanks for the response. Unfortunately I really don't want to use
gatsby-ssr.js because I want the ability to be able to optionally
include the Buy Me A Coffee script in my blog posts. – Mac-Mann 4
hours ago
You can try something like this:
Create a function that will render asynchrounously on-demand the script:
const addExternalScript = (url, callback) => {
const script = document.createElement('script');
script.src = url;
script.async=true;
script.onload = callback;
// add as many parameters as you need.
document.body.appendChild(script);
};
You can also remove the callback function parameter if you don't need it despite being useful.
Call them in your componentDidMount() (that will make the trick since you need to wait for the DOM tree loading until access to a document global object):
componentDidMount(){
addExternalScript(`https://cdn.buymeacoffee.com/widget/1.0.0/prod/widget.prod.min.js`)
}

Is componentDidMount causing html.script to disappear?

I am having issues mounting an external script into a component of my React/Gatsby App. The script below is called into a component that is used in two places throughout app.
First being pages/index.js and loads fine with zero issue, yet when called to use within a gatsby created page (exports.createPages = ({ graphql, boundActionCreators }) => {) from a template the script will load, show content and then go.
Here is the code for the script being mounted into the component -
componentDidMount () {
const tripadvisor = document.createElement("script");
tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
tripadvisorLeft.async = true;
document.body.appendChild(tripadvisor);
}
I am not getting any errors from the console.
NOTE: Incase of relation to the error? I also have this code using componentDidMount and componentWillUnmount in the /layout/index.js file that handles a body class for navigation elements.
componentDidMount () {
this.timeoutId = setTimeout(() => {
this.setState({loading: ''});
}, 100);
this.innerContainer.addEventListener("scroll", this.handleHeaderStuck), 100;
this.innerContainer.addEventListener("scroll", this.handleSubNavStuck), 200;
}
componentWillUnmount () {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.innerContainer.removeEventListener("scroll", this.handleHeaderStuck);
this.innerContainer.removeEventListener("scroll", this.handleSubNavStuck);
}
UPDATE: All code
import React from 'react';
import Link from 'gatsby-link'
import styled from 'styled-components'
const Wrapper = styled.section`
display:block;
`
class ReviewsPage extends React.Component {
componentDidMount () {
const tripadvisorLeft = document.createElement("script");
tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
tripadvisorLeft.async = true;
document.body.appendChild(tripadvisorLeft);
}
render() {
return (
<Wrapper id="tripAdvisor">
<div id="TA_selfserveprop789" className="TA_selfserveprop">
<ul id="3LacWzULQY9" className="TA_links 2JjshLk6wRNW">
<li id="odY7zRWG5" className="QzealNl"></li>
</ul>
</div>
</Wrapper>
)
}
}
export default ReviewsPage
So, all your componentDidMount() is doing is adding a <script> tag which references a third party script. I am assuming that third party script tries to add some information or thing to the DOM (something you can see visually).
However, the DOM only exists between component updates. React will completely redraw the DOM (the HTML inside your component) any time it detects a change to State or Props. I'm assuming in this case that Wrapper is what is resetting each time.
I'm not sure how to help with this, mainly because React's entire role in an application is really just managing the state of the DOM, and that script is trying to edit the DOM, but without telling React. React might be sensing an invalid change to the DOM then trying to correct it, but I really don't think React does that. At any rate, the issue is that React is trying to manage the DOM while another thing is trying to edit the DOM, and that's not gonna end well.
It would be better if you could have a script that asynchronously calls to the other service and receives data, then let React apply that data to the DOM, instead of letting the script edit the DOM itself. Granted, you probably don't have control over how that external script actually works, which is why I say I'm not sure how to help.

Categories

Resources