html.js loading the script only once on page load Gatsby - javascript

I am using html.js to load custom script. I have created a js file in static folder custom.js but when I am running my project it loads the script only once on first time page load but when I am navigation to other page it not loading the script.
My custom.js file
$(document).ready(function () {
console.log("in ready");
});
My html.js file
import React from "react";
import PropTypes from "prop-types";
import { withPrefix } from "gatsby";
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"
></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css"
rel="stylesheet"
type="text/css"
/>
{props.headComponents}
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: props.body }}
/>
{props.postBodyComponents}
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script
type="text/javascript"
src={withPrefix("js/custom.js")}
></script>
</body>
</html>
);
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
};
What I am doing here wrong ? Why it's loading script only once ? What I have to do load custom.js script on every page navigation?
I have also tried to include custom.js inside my Layout file in <Helmet></Helmet> but same issue.
Thanks

What I am doing here wrong ? Why it's loading script only once ? What
I have to do load custom.js script on every page navigation?
I think that there is a misunderstanding and a mix of context on how React and old-fashioned scripting like jQuery works. In the end, Gatsby is a React-based application.
Among saying that Reacts manipulates a virtual DOM (vDOM) and jQuery points directly to the DOM, which has an extremely high-cost impact on performance. If you mix both approaches outside the scope of React, you can block React's hydration, potentially breaking your application.
You can simply create a useEffect hook with empty dependencies ([]), which will be triggered once the DOM tree is loaded for each page it's included. React's lifecycle should work for your use-case without overkilling the customization of the HTML generated by Gatsby.
Your custom.js file must have something exported. As simply as:
const customJS =()=> console.log("I'm ready");
export default customJS;
Then, on any page:
import React, {useEffect} from "react";
import customJS from "../path/to/custom.js";
const AnyPage =(props)=>{
useEffect(()=>{
customJS()
}, [])
return <div>Some content for AnyPage()<div>
}
export default AnyPage;
customJS() will be triggered only one time per page, once the DOM is loaded.
Please, don't use jQuery along with React, it's not needed.

Related

Can I run index.html with React without npm start and npx create-react-app?

I am self-learning react and I am just confused about a lot of things.
I thought that if I add React to my index.html via a script like the below:-
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bill Details</title>
</head>
<body>
<div id="billTable"></div>
<script src="BillTable.js" type="text/javascript"></script> ------------- Problem Line 1
</script>
</body>
</html>
and this is my js file where I am trying to return react component
//BillTable.js
import React from "react";
import ReactDOM from "react-dom";
function BillTable() {
return <h1>HELLO TABLE</h1>;
}
ReactDOM.render(<BillTable/>, document.getElementById("billTable"));
when I try to open index.html directly in firefox or through express server I get the below error in console:-
Uncaught SyntaxError: import declarations may only appear at top level of a module.
I then got rid of this error by changing the script type in problem line 1 in index.html to
<script src="BillTable.js" type="text/babel"></script>
but then also my webpage is completely blank and even console is not showing any errors.
Please suggest how to solve this issue. I am right now trying to learn React with functional approach only, so if any changes are required to be done on the react side, please make them in the functional approach.
I don't think you have included the correct packages to handle React components and JSX yet. These packages react, react-dom, etc. are usually in a package.json and are required to tell the browser what tools will be used to run the code. These packages handle the "script" or components you create and places the elements constructed in your components to the DOM. You can solve this by loading react with additional script tags before your component's script tag. This will let the browser know how and what to use to run your react component. Also, in your function, it does not know that it is a React Component. Check out an explanation for why you would have to use React.createElement I have attached an example of using only an index.html page here:
example of using an index.html page
Your Component file:
"use strict";
function BillTable() {
return React.createElement("h1", "", "HELLO TABLE");
}
const domContainer = document.querySelector("#billTable");
const root = ReactDOM.createRoot(domContainer);
root.render(React.createElement(BillTable));
and your index.html:
<body>
<div id="billTable"></div>
<!-- Load your React packages -->
<script
src="https://unpkg.com/react#18/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"
crossorigin
></script>
<!-- Load your React component. -->
<script src="BillTable.js"></script>
</body>

Next Js fails to load third party script in _document.js [duplicate]

I try to understand how the next.js Script tag with the strategy beforeInteractive works. For testing i just used lodash. But i keep getting a ReferenceError: _ is not defined. I thought when a script is loaded with beforeInteractive it should be globally available inside my page Component since it get injected into the initial Html from the server and i could use it for example in the useEffect hook to alter a div.
Can someone explain to me why it's not working or what i'm doing wrong?
I don't installed it via npm because im trying to figure out how it works.
I have a simple _document.js and i added a Next.js script tag with the strategy beforeInteractive to this _document.js. The next.js docs says:
This strategy only works inside _document.js and is designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
import { Html, Head, Main, NextScript } from 'next/document'
import Script from 'next/script'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<Script
src="https://unpkg.com/lodash#4.17.20"
strategy="beforeInteractive"
></Script>
</body>
</Html>
)
}
Then i have a simple page Component inside the pages folder. I added the getServerSideProps function to use ServerSideRendering.
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
import Head from 'next/head';
import {useEffect, useState} from 'react';
const TestComponent = () => {
const [change,setChange] = useState('not changed');
useEffect(()=> {
console.log(_);
setChange(_.join(['one','two'],' - '));
});
return (
<>
<Head>
<title>Test</title>
</Head>
<div>{change}</div>
</>
);
};
export async function getServerSideProps(context) {
return {
props: {},
}
}
export default TestComponent;
Update
Seems like it is indeed a bug which is fixed but not released yet
https://github.com/vercel/next.js/discussions/37098
Putting aside the fact that you should be importing Lodash as a node module, there does seem to be an issue when using next/script in _document (no matter what the external script actually is).
It turns out this is a Next.js bug that has been addressed in this PR in pre-release version v12.1.7-canary.8. To fix the issue in your project simply update Next.js to version >=12.2.0 (npm install next#latest).
As an alternative, you can use the <script> tag directly in the _document's <Head> with the defer property. This closely matches what the next/script would output.
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<script
type="text/javascript"
src="https://unpkg.com/lodash#4.17.20/lodash.js"
defer
></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
First and foremost, I'm failing to see virtually any reason you'd want to do this, when you can (and should) simply use install it to node_modules. You're also going to possibly run the risk of the bundle having issues if the library type isn't a module and the next configuration requires a module.
Solution based on the question:
There's two ways.
Firstly, see the docs on this exact thing.
Please use the above method mentioned in the docs.
If that's not an option for whatever reason...
The second is a less than ideal, but working solution.
Create a folder for your static files. Ex: <root>/static/js/hello.js. Then in your _document file,
<script type="text/javascript" src="/static/hello.js"></script>

How to work with CDN script only in Vue... unable to import component?

I am a newbie to Vue.js. I don't like working with cli, so I am using CDN for everything but stuck in some unknown problem. I googled many thing but couldn't understand what going on. Any help would be really appreciated.
Here is my index.php
<!DOCTYPE html>
<html>
<head>
<meta charset="8-utf" />
<meta name="author" content="Yash Gaikwad">
<meta name="description" content="">
<meta name="keyword" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="style.css"> <!--Stylesheet-->
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400&display=swap" rel="stylesheet"> <!--Google Fonts-->
<script src="https://www.w3schools.com/lib/w3.js"></script> <!--W3.Css-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/6.1.19/browser.js" type="text/babel"></script><!--Bable Hosting-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--Vue Hosting-->
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <!--Vue Router-->
</head>
<body>
<div id="app">
</div>
<script src="script.js"></script> <!--Javascript-->
</body>
</html>
Here is my script.js
import app from "./main.vue"
new Vue({
el: "#app",
data: {
},
methods: {
},
render: h => h(app)
});
This is my main.vue (root component)
<template>
<app-header></app-header>
<app-footer></app-footer>
</template>
<script>
import Header from "./components/header.vue";
import Footer from "./components/footer.vue";
export default {
components: {
"app-header": Header,
"app-footer": Footer
}
}
</script>
<style scoped>
</style>
Seems to me that importing main.vue is causing error. And something is going wrong there.
This are the errors I being getting from both ff and chrome
Uncaught SyntaxError: Cannot use import statement outside a module
SyntaxError: import declarations may only appear at top level of a module
Thankyou every much guys.
.vue is not a file type that a browser understands. If you want to build VueJS applications without using any CLI tools you'll have to learn about ES6 Modules and understand that your application won't be supported by all browsers.
CLI tools like Webpack transforms your JavaScript and Vue code into JavaScript code that is compatible with more browsers, minifies your code (makes the size of the files that are downloaded smaller), etc. It also makes it much easier for you to use third-party packages in your code, as well as to keep them updated.
Using a CLI tool like Webpack or VueCLI for Vue apps will make your life much easier in the long run and is the standard way of doing things across the industry.
Always import statement should be at the top of the file,
you are using template and then the import
in main.vue put script block to the top and then the template
The browser cannot understand .vue extension while using vue CDN so replace that with .js or load vue via CLI instead.
Next inside the JS (vue) files, remove the template, style and script tag and make it look like this:
//header.js
export default {
data: () => ({
}),
template:`<h1>Header</h1>`
}
//footer.js
export default {
data: () => ({
}),
template:`<h1>Footer</h1>`
}
Next, add a type module to your script tag like:
<script type="module">
import Header from "./components/header.js";
import Footer from "./components/footer.js";
export default {
components: {
"app-header": Header,
"app-footer": Footer
}
}
</script>
You can also replace ES6 import statement with ES5 require if you don't want to use type="module"

How do I inject data from the server into a Vue CLI app?

I have a Vue CLI app that I want to connect to a server back-end.
The server will return a view template with a data payload, and I want to inject that payload as JSON into the Vue app as a data property.
The index.html file generated by Vue CLI looks like the following (I set filenameHashing to false in vue.config.js to avoid random file names):
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=UTF-8>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">
<link href=/css/app.css rel=preload as=style>
<link href=/css/chunk-vendors.css rel=preload as=style>
<link href=/js/app.js rel=preload as=script>
<link href=/js/chunk-vendors.js rel=preload as=script>
<link href=/css/chunk-vendors.css rel=stylesheet>
<link href=/css/app.css rel=stylesheet>
</head>
<body>
<div id=app>
</div>
<script src=/js/chunk-vendors.js></script>
<script src=/js/app.js></script>
</body>
</html>
And main.js looks like the following:
import Vue from 'vue';
import SiteContainer from './components/layout/SiteContainer.vue';
new Vue({
render: h => h(SiteContainer)
}).$mount('#app');
I want to be able to do something like the following:
<div id="app" :data="{{ json-string-of-data-payload-from-server }}">
So that I can then access the data in SiteContainer as follows:
props: [
'data'
]
I've tried essentially doing what I wrote above, but because the render method seems to completely replace the #app div with the site-container component, the :data property is lost as well.
I also saw that you can pass a second argument to render with data, but I couldn't figure out how to make it work, and ultimately, that's in main.js, not the view template file that the server will use, which is where I need the JSON data to be to get it from the server to the Vue app.
How can I pass data from the server-generated view template to the Vue CLI app to get everything going? Thank you.
I was able to figure it out.
By defining the SiteContainer component as a Vue.js component in main.js first, I am able to freely use the component in the HTML file to get what I want.
Specifically, I changed the main.js file as follows:
import Vue from 'vue';
import SiteContainer from './components/SiteContainer.vue';
Vue.component('SiteContainer', SiteContainer); // This is the key.
new Vue({
el: '#app'
});
And then the body part of the HTML file becomes the following (i.e., replace the #app div that I had in my question above):
<SiteContainer id="app" :data="{{ json-string-of-data-payload-from-server }}">
</SiteContainer>

Next.js Loads <script> tags but it doesn't execute them

I'm integrating an existing React app into Next.js for mainly SEO features.
i pasted the css files links inside the <Header> tag in Next.js and they seem to work just fine. when i tried to do the same with the javascript files using the <script> tag, it doesn't execute the code inside those scripts. but i can see them loaded with http 200 in the chrome dev tools.
I tried using a library called Loadjs from npm to load the scripts inside componentDidMount but i got the exact same result as using <script> tag.
is there a proper way to do such simple thing in Next.js that i'm not aware of ?
Here's the code included in the pages/index.js file.
import React from "react"
import Head from 'next/head'
import MyAwesomeComponent from "../components/mycomponent.js"
export default () => (
<div>
<Head>
<link type="text/css" rel="stylesheet" href="static/css/chatwidget.css" />
<link type="text/css" rel="stylesheet" href="static/css/download.css" />
<script type="text/javascript" src="static/libs/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/malihu-custom-scrollbar-plugin#3.1.5/jquery.mCustomScrollbar.concat.min.js"></script>
<script type="text/javascript" src="static/libs/bootstrap.min.js"></script>
<script type="text/javascript" src="static/libs/owl.carousel.min.js"></script>
<script type="text/javascript" src="static/scripts/chatHead.js"></script>
<script type="text/javascript" src="static/libs/jquery.magnific-popup.js"></script>
</Head>
<MyAwesomeComponent /> {/* a simple React component that returns : <p>Hello World </p>*/}
</div>
)
Sorry for the late answer.
it turned out that all the scripts i linked missed one script that would actually run the functions for each action.
This works to me:
Create a folder for your static files:
<root>/public/static/script.js
in your index.js at <root>/pages/index.js
import Head from 'next/head';
import MyAwesomeComponent from '../components/mycomponent';
export default () => (
<div>
<Head>
<script type="text/javascript" src="/static/script.js"></script>
</Head>
<MyAwesomeComponent />
</div>
)
Note that static is the name I used in this example, it's not a requirement, it would work with any other folder name.
With the below approach you can easily put a script file's raw script text into a generated Next.js HTML page's <head> without screwing around with character escaping, formatting and general pretending that we aren't actually just building an HTML page in the end anyways.
There are many use cases you may want a script to run without going to network. Ex: 3rd party scripts, monitoring / analytics scripts that expect to be in the <head> without a separate network load. Many of these come minified, mangled, you-name-it and are just supposed to be copy, paste, move on with life.
Next.js makes this very hard pretending that everything with web development is magically React and Webpack all the time now (I wish right?)
The best developer experience way I've found is to do this:
_document.js
...
<Head>
<script type="text/javascript" dangerouslySetInnerHTML={{ __html: process.env.rawJsFromFile }}></script>
</Head>
...
next.config.js
https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config.ts#L33
module.exports = {
env: {
rawJsFromFile: fs.readFileSync('./rawJsFromFile.js').toString()
}
}
rawJsFromFile.js
alert('Freedom!!!!!!!!!!!!!!!');
// and other 3rd party script junk, heck even minified JS is fine too if you need
Hope this saves someone from hours of frustration that I endured coming up with this... 😭
You can also run js code this
<script
dangerouslySetInnerHTML={{
__html: `
let a = 1;
functionCall();
`,
}}
></script>
With Next.js v11 and onward, you can use the Next component Script
https://nextjs.org/blog/next-11#script-optimization
<Script
src="..."
strategy="beforeInteractive"
/>
May this helps you Nextjs public folder
Move your static folder into public folder in your root directory
export default () => (
<div>
<Head>
<link type="text/css" rel="stylesheet" href="/static/css/chatwidget.css" />
<link type="text/css" rel="stylesheet" href="/static/css/download.css" />
<script type="text/javascript" src="/static/libs/jquery.min.js"></script>
...
</Head>
<MyAwesomeComponent />
</div>
)
This is what I tried and it worked for me.
I used two files entry-script.js and main-script.js. I put these like this
<root>/static/entry-script.js and <root>/static/main-script.js
The content of entry-script.js is below.
(function (d, t) {
t = d.createElement("script");
t.setAttribute("src", "/static/main-script.js");
d.getElementsByTagName("head")[0].appendChild(t);
})(document);
and the main logic is in the file main-script.js.
In the file _doucment.js of NextJS I included my file entry-script.js in body like below
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<script
type="text/javascript"
src="/static/entry-script.js"
></script>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
I wrote an article elaborating on this question, hopefully it comes in handy:
https://www.devtwins.com/blog/how-to-add-a-third-party-script-to-a-nextjs-website
Or if you want to try another way to import Js file like I did
import { useEffect } from "react";
function MyApp({ Component, pageProps }) {
useEffect(() => {
import("../styles/bootstrap.bundle.min.js");
}, []);
return <></>
};

Categories