My Current Setup
I have a Google Tag Manager script tag placed in the _document.tsx file nested under the <Head> component:
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `<!-- Google Tag Manager -->`,
}}
/>
</Head>
<body>
<noscript
dangerouslySetInnerHTML={{
__html: `<!-- Google Tag Manager (noscript) -->`,
}}
/>
<style
dangerouslySetInnerHTML={{
__html: (this.props as any).globalStyles,
}}
></style>
<Main />
<NextScript />
</body>
</Html>
)
}
}
My Goal
We have certain pages set up that are meant for international customers, and those pages all have a "-INTL" at the end of the url (for example: https://www.mystore.com/product--INTL).
My goal is to have Google Tag Manager NOT render if it is an international link, which contains the "--INTL" string in the URL.
Ideally, I want to wrap the entire Google Tag Manager script into its own component, then conditionally render that based on the URL of the page.
Ideal Setup that doesn't work
GTM Component Code:
class GTMComponent extends React.Component {
let currentUrl;
componentDidMount(){
if(typeof window !== "undefined"){
currentUrl = window.location.href;
}
}
render(){
if(!currentUrl.contains("--INTL")){
return (
<script dangerouslySetInnerHTML={{__html: `<!--
Google Tag Manager Script -->`}}/>
)
}
else {
return (
<></>
)
}
}
}
export default GTMComponent
_document.tsx file with GTMComponent:
import GTMComponent from '#components/common/GTMComponent'
class MyDocument extends Document {
render(){
return(
<HTML>
<Head>
<GTMComponent/>
</Head>
<body>
{/* body code */}
</body>
</HTML>
)
}
}
export default MyDocument
My Problem
I want to be able to access the window object, but due to Server Side Rendering, the window object is never defined.
Additionally, I can't use useEffect since _document.tsx is a class-based component, and I weirdly can't get componentDidMount to work either.
I have a react app embedded in a webpage and the app should start with parameters it gets from the page. Now I pass the parameters on index.HTML file in a div but the problem is if the parameters are updated the app won't notice the changes.
index.HTML
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Sales flow</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div class="staringId" packageId="2" itemId="5" channelId="9"></div>
</body>
</html>
index.js
import './index.scss';
import App from './App'
const div = document.querySelector('.staringId')
ReactDOM.render(
<App domElement={div} />,
div
);
App.js
const App = ({ domElement }) => {
const package = domElement.getAttribute("packageId")
const item = domElement.getAttribute("itemId")
const channel = domElement.getAttribute("channelId")
const [data, setData] = useState({
packageId: '',
itemId: '',
channelId: '',
})
useEffect(() => {
setSelData({
packageId: package,
itemId: item,
channelId: channel,
})
}, [package, item, channel])
}
javascript file to embed the app
<div class="staringId" packageId="2" itemId="5" channelId="9"</div>
<script src="./dist/runtime-main.8d32315e.js"></script>
<script src="./dist/2.4c89be1e.chunk.js"></script>
<script src="./dist/main.a41deb26.chunk.js"></script>
My question is how I can pass some external parameters to the app and update(restart) the app after each update to the parameters.
The idea is to have only one top-level component in your HTML, and whatever parameters(props) you are trying to pass sit on the other components within the top-level . Following is the rough structure how it would look like.
index.html
<html>
<body>
<div id="app"></div>
</body>
</html>
index.js
import './index.scss';
import App from './App'
ReactDOM.render(
<App packageId="2" itemId="5" channelId="9" />, // you can choose to dynamically pass the value depends on what you get from page
document.getElementById("app")
);
App.js
const App = ( props ) => {
// props.packageId and etc are available here
// you can then choose to render it with these props value
return (
<MyOtherComponent otherProps="ifany" />
);
}
export default App;
In NextJs application, I want to add <script> code but not getting where to add and how? I tried by adding it in .js file but didn't recognise.
In react, we have index.html to add these things, what about in Nextjs app?
To edit the index.html, the equivalent in NextJS is the _document.js file.
A custom Document is commonly used to augment your application's <html> and <body> tags.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<script>...</script>
</body>
</Html>
)
}
}
If you only want to append elements to <head> tag (for a specific page), use next/head:
import Head from 'next/head'
function IndexPage() {
return (
<>
<Head>
<script>....</script>
</Head>
</>
)
}
I am aiming to use a Vanilla JavaScript package in a more sophisticated React app to build additional logic around the JavaScript package.
The JavaScript library is LabelStudio and docs can be found here: https://github.com/heartexlabs/label-studio-frontend
However, when I try to import the LabelStudio I get an error saying Module not found: Can't resolve 'label-studio' , as described here https://github.com/heartexlabs/label-studio-frontend/issues/55
Since my understanding of frontend code is limited, I am not sure whether this is something the developers did not expected users to do and just wanted them to use the entire library and customized instead of using the library as a component. My idea was to use the library as in the vanilla javascript example here:
<!-- Include Label Studio stylesheet -->
<link href="https://unpkg.com/label-studio#0.7.1/build/static/css/main.0a1ce8ac.css" rel="stylesheet">
<!-- Create the Label Studio container -->
<div id="label-studio"></div>
<!-- Include the Label Studio library -->
<script src="https://unpkg.com/label-studio#0.7.1/build/static/js/main.3ee35cc9.js"></script>
<!-- Initialize Label Studio -->
<script>
var labelStudio = new LabelStudio('label-studio', {
config: `
<View>
<Image name="img" value="$image"></Image>
<RectangleLabels name="tag" toName="img">
<Label value="Hello"></Label>
<Label value="World"></Label>
</RectangleLabels>
</View>
`,
interfaces: [
"panel",
"update",
"controls",
"side-column",
"completions:menu",
"completions:add-new",
"completions:delete",
"predictions:menu",
],
user: {
pk: 1,
firstName: "James",
lastName: "Dean"
},
task: {
completions: [],
predictions: [],
id: 1,
data: {
image: "https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg"
}
},
onLabelStudioLoad: function(LS) {
var c = LS.completionStore.addCompletion({
userGenerate: true
});
LS.completionStore.selectCompletion(c.id);
}
});
</script>
How can I make use of the above code in a React Component to facilitate dynamic data loading and use of state to customize the functions?
I don't have a solution making the npm module label-studio to work. I tried importing the dist file instead, but it errors
Expected an assignment or function call and instead saw an expression
So here's a workaround until the maintainers address this.
Copy the JS file from build/static/js, then place it in a script in the public folder on index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="%Your_Path_To_Label-Studio%/main.js"></script>
</body>
</html>
The script file defines a global function variable, so you can access it in React by using the window object. The useEffect hook is to make sure the initialization is only run once.
import React, { useEffect, useRef } from "react";
function App() {
const LabelStudio = window.LabelStudio; // label-studio script stores the api globally, similar to how jQuery does
const myLabelStudioRef = useRef(null); // store it and then pass it other components
useEffect(() => {
myLabelStudioRef.current = new LabelStudio("label-studio", {
config: `
<View>
<Image name="img" value="$image"></Image>
<RectangleLabels name="tag" toName="img">
<Label value="Hello"></Label>
<Label value="World"></Label>
</RectangleLabels>
</View>
`,
interfaces: [
"panel",
"update",
"controls",
"side-column",
"completions:menu",
"completions:add-new",
"completions:delete",
"predictions:menu",
],
user: {
pk: 1,
firstName: "James",
lastName: "Dean",
},
task: {
completions: [],
predictions: [],
id: 1,
data: {
image:
"https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg",
},
},
onLabelStudioLoad: function (LS) {
var c = LS.completionStore.addCompletion({
userGenerate: true,
});
LS.completionStore.selectCompletion(c.id);
},
});
}, []);
return (
<div className="App">
{/* Use Label Studio container */}
<div id="label-studio"></div>
</div>
);
}
export default App;
As far as storing the new instance of LabelStudio, there's many ways to go about it. You can store it as variable on the root component using either useState or useRef hooks and then pass it to child components. If you want to avoid manually passing variable down the component tree, then you need a state manager such as React Context or Redux.
This is the basic setup using LabelStudio as a module in a React component.
yarn add #heartexlabs/label-studio
import LabelStudio from '#heartexlabs/label-studio'
import { useEffect } from 'react'
import '#heartexlabs/label-studio/build/static/css/main.css'
const ReactLabelStudio = () => {
useEffect(() => {
new LabelStudio('label-studio', {
config: {...},
interfaces: [...],
user: {...},
task: {...},
onLabelStudioLoad: function(LS) {
const c = LS.annotationStore.addAnnotation({
userGenerate: true,
});
LS.annotationStore.selectAnnotation(c.id);
}
})
}, [])
return <div id="label-studio" />
}
export default ReactLabelStudio
Exploring NextJS a bit for its server side rendering features. It looks really nice and easy to use. I already explored the _document.js file which we can include to overwrite the default. I found the following code in an example:
import Document, { Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {
return (
<html>
<Head>
<link rel="stylesheet" href="/_next/static/style.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
Now I get it that we are overwriting the current HTML template. But I'm a bit confused regarding the functionality of the Main and Nextscript.
Is Main just a page? What is Nextscript (which script)?
NextScript Class is here
and Main Class is here and I copied the same below. Main renders html/ errorHtml from this.context._documentProps
export class Main extends Component {
static contextTypes = {
_documentProps: PropTypes.any
}
render () {
const { html, errorHtml } = this.context._documentProps
return (
<Fragment>
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
<div id='__next-error' dangerouslySetInnerHTML={{ __html: errorHtml }} />
</Fragment>
)
}
}
you can find actual documentation on Custom Document here
For those who will look for an answer to this question in the future...
Component NextScript from 'next/document' takes a list of files from context._documentProps and returns each of them as a element like this:
<script
key={file}
src={`${assetPrefix}/_next/${file}`}
nonce={this.props.nonce}
async
/>
It also takes dynamic imports from context._documentProps and returns them in a similar way:
<script
async
key={bundle.file}
src={`${assetPrefix}/_next/${bundle.file}`}
nonce={this.props.nonce}
/>