Extend background video and navigation bar across all pages in Gatsby - reactjs

I want to have my background video and navigation bar spread across all pages I make but I also want them to be behind all of the other components rendered. I understand that I need to wrap the root element inside of gatsby-browser.js but the problem I have been faced with is that the background covers all of the elements. Is there any way to fix this?
Current gatsby-browser.js - Currently covers all components with background.
import React from 'react';
import BackGround from "../../src/components/bg"
export const wrapPageElement = ({ element }) => {
return <BackGround>{element}</BackGround>;
};
"use strict";
var _interopRequireDefault = require("#babel/runtime/helpers/interopRequireDefault");
var _interopRequireWildcard2 = _interopRequireDefault(require("#babel/runtime/helpers/interopRequireWildcard"));
/* global __PATH_PREFIX__ */
// Taken from https://github.com/netlify/netlify-identity-widget
var routes = /(confirmation|invite|recovery|email_change)_token=([^&]+)/;
var errorRoute = /error=access_denied&error_description=403/;
var accessTokenRoute = /access_token=/;
export const onInitialClientRender = function (_, _ref) {
var _ref$enableIdentityWi = _ref.enableIdentityWidget,
enableIdentityWidget = _ref$enableIdentityWi === void 0 ? true : _ref$enableIdentityWi,
_ref$publicPath = _ref.publicPath,
publicPath = _ref$publicPath === void 0 ? "admin" : _ref$publicPath;
var hash = (document.location.hash || "").replace(/^#\/?/, "");
if (enableIdentityWidget && (routes.test(hash) || errorRoute.test(hash) || accessTokenRoute.test(hash))) {
Promise.resolve().then(function () {
return (0, _interopRequireWildcard2.default)(require("netlify-identity-widget"));
}).then(function (_ref2) {
var netlifyIdentityWidget = _ref2.default;
netlifyIdentityWidget.on("init", function (user) {
if (!user) {
netlifyIdentityWidget.on("login", function () {
document.location.href = __PATH_PREFIX__ + "/" + publicPath + "/";
});
}
});
netlifyIdentityWidget.init();
});
}
};
If any additional information is needed please let me know.

I don't think that wrapPageElement nor wrapRootElement APIs fits your requirements since they will wrap your entire page in a provided component, and that's what you are trying to avoid. Of course, they will prevent your component to be unmounted, however, this will be automatically handled by #reach/routing with the following approach.
What you are trying to achieve it's a simple shared component across all pages that extends from your <Layout>:
const Layout = ({ children }) => {
return <section>
<BackGround>
<YourNavigationComponent />
</BackGround>
<main>{children}</main>
</section>;
};
Something like this will wrap your <YourNavigationComponent /> with your background component across all site, avoiding the wrapping of the whole elements.

Related

How do I access the 'currentImageIdIndex' when using the stack scroll tool in Cornerstone.js in a React functional component?

I'm currently successfully displaying a stack of images in a React component but am unsure where to place an event listener in order to access the currentImageIdIndex when scrolling.
import React, { useEffect, useRef, useCallback } from "react";
import cornerstone from "cornerstone-core";
import cornerstoneMath from "cornerstone-math";
import cornerstoneTools from "cornerstone-tools";
import cornerstoneFileImageLoader from "cornerstone-file-image-loader";
import Hammer from "hammerjs";
function StackImageViewport(props) {
const viewerRef = useRef(null);
const base64StringToArrayBuffer = useCallback((base64) => {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}, []);
const initializeCornerstone = () => {
// Initialise cornerstone and link to DOM element
cornerstoneTools.external.cornerstone = cornerstone;
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
cornerstoneFileImageLoader.external.cornerstone = cornerstone;
cornerstoneTools.external.Hammer = Hammer;
cornerstoneTools.init();
cornerstone.enable(viewerRef.current);
};
const setCornerstoneTools = () => {
// define Cornerstone Tools
const StackScrollTool = cornerstoneTools.StackScrollTool;
const StackScrollMouseWheelTool =
cornerstoneTools.StackScrollMouseWheelTool;
const WindowingTool = cornerstoneTools.WwwcTool;
// Add tools
cornerstoneTools.addTool(StackScrollTool);
cornerstoneTools.addTool(StackScrollMouseWheelTool);
cornerstoneTools.addTool(WindowingTool);
// set tools to Active state
cornerstoneTools.setToolActive("StackScroll", { mouseButtonMask: 1 });
cornerstoneTools.setToolActive("StackScrollMouseWheel", {});
cornerstoneTools.setToolActive("Wwwc", { mouseButtonMask: 2 });
};
const displayStack = (stackMediaArray) => {
let mediaArray = [];
// 'stackMediaArray' is an array of images, each containing a buffer of the image
Promise.all(
stackMediaArray.map((mediaObject) => {
return new Promise((resolve, reject) => {
let imageBuffer = base64StringToArrayBuffer(mediaObject.buffer);
const imageId =
cornerstoneFileImageLoader.fileManager.addBuffer(imageBuffer);
mediaArray.push(imageId);
resolve(mediaObject);
}).catch(console.error);
})
);
//define the stack
const stack = {
currentImageIdIndex: 0,
imageIds: mediaArray,
};
// load images and set the stack
cornerstone.loadAndCacheImage(mediaArray[0]).then((image) => {
cornerstone.displayImage(viewerRef.current, image);
cornerstoneTools.addStackStateManager(viewerRef.current, ["stack"]);
cornerstoneTools.addToolState(viewerRef.current, "stack", stack);
});
setCornerstoneTools();
};
useEffect(() => {
if (!viewerRef.current) {
return;
}
initializeCornerstone();
displayStack(props.stackMediaArray);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewerRef]);
return (
<>
<div ref={viewerRef} id="viewer" className="flex h-1/2-screen"></div>
</>
);
}
export default StackImageViewport;
This attempts to answer the question:
https://github.com/cornerstonejs/cornerstoneTools/issues/1121
however, I don't want to access DOM elements to add the event listener to.
It's also clear that events are accessible in Cornertsone.js:
https://github.com/cornerstonejs/cornerstoneTools/blob/master/src/events.js
... but I'm still not sure where to place the event listener ?
Any help would be much appreciated.
Thanks.
Listening to events in ReactJS might be confusing for someone new, even more, when using a new tool such as CornerstoneJS. However, you can create an event listener by using the window.addEventListener method, just like you would in a Vanilla JavaScript. Note that this might change from browser to mobile environments. Moreover, your cornerstoneFileImageLoader can also be a challenge.
For this purpose, you can follow the structure:
window.addEventListener('keydown', (event) => {
...
});
But now, we need to understand "where" to place it. Imagine that all of your pages are just components, as ReactJS is a component-based system. Meaning that the event listener need to happen inside the component.
For instance, you can do like that:
import React from 'react';
const App = (props) => {
window.addEventListener('keydown', (event) => {
...
});
return (
<div className='container'>
<h1>Welcome to the Keydown Listening Component</h1>
</div>
);
};

Lazy Hydrate + Code Splitting on NextJS app

I know how to do Lazy Hydration and I know how to do Code Splitting, but how can I make the splitted chunck download only when the component is hydrating?
My code looks like this
import React from 'react';
import dynamic from 'next/dynamic';
import ReactLazyHydrate from 'react-lazy-hydration';
const MyComponent = dynamic(() => import('components/my-component').then((mod) => mod.MyComponent));
export const PageComponent = () => {
return (
...
<ReactLazyHydrate whenVisible>
<MyComponent/>
</ReactLazyHydrate>
...
);
};
MyComponent is rendered below the fold, which means that it is only gonna hydrate when the user scrolls. The problem is that the JS chunck for MyComponent will be downloaded right away when the page loads.
I was able to hack it by using the dynamic import only on client but this makes the component disappear for a second when it hydrates, because the html rendered on server will not be used by react. It will recreate the DOM element and it will be empty until the JS chunck loads.
When the element disappear for a sec it increases the page CLS and that's the main reason why I can not use this hack.
Here is the code for this hack
const MyComponent = typeof window === 'undefined'
? require('components/my-component').MyComponent
: dynamic(() => import('components/my-component').then((mod) => mod.MyComponent));
Note that I want to render the component's HTML on the server render. That't why I don't want to Lazy Load it. I want to Lazy Hydrate so I can have the component's HTML rendered on server but only download
and execute it's JS when it is visible.
Update:
In document:
// stops preloading of code-split chunks
class LazyHead extends Head {
getDynamicChunks(files) {
const dynamicScripts = super.getDynamicChunks(files);
try {
// get chunk manifest from loadable
const loadableManifest = __non_webpack_require__(
'../../react-loadable-manifest.json',
);
// search and filter modules based on marker ID
const chunksToExclude = Object.values(loadableManifest).filter(
manifestModule => manifestModule?.id?.startsWith?.('lazy') || false,
);
const excludeMap = chunksToExclude?.reduce?.((acc, chunks) => {
if (chunks.files) {
acc.push(...chunks.files);
}
return acc;
}, []);
const filteredChunks = dynamicScripts?.filter?.(
script => !excludeMap?.includes(script?.key),
);
return filteredChunks;
} catch (e) {
// if it fails, return the dynamic scripts that were originally sent in
return dynamicScripts;
}
}
}
const backupScript = NextScript.getInlineScriptSource;
NextScript.getInlineScriptSource = (props) => {
// dont let next load all dynamic IDS, let webpack manage it
if (props?.__NEXT_DATA__?.dynamicIds) {
const filteredDynamicModuleIds = props?.__NEXT_DATA__?.dynamicIds?.filter?.(
moduleID => !moduleID?.startsWith?.('lazy'),
);
if (filteredDynamicModuleIds) {
// mutate dynamicIds from next data
props.__NEXT_DATA__.dynamicIds = filteredDynamicModuleIds;
}
}
return backupScript(props);
};
in next config
const mapModuleIds = fn => (compiler) => {
const { context } = compiler.options;
compiler.hooks.compilation.tap('ChangeModuleIdsPlugin', (compilation) => {
compilation.hooks.beforeModuleIds.tap('ChangeModuleIdsPlugin', (modules) => {
const { chunkGraph } = compilation;
for (const module of modules) {
if (module.libIdent) {
const origId = module.libIdent({ context });
// eslint-disable-next-line
if (!origId) continue;
const namedModuleId = fn(origId, module);
if (namedModuleId) {
chunkGraph.setModuleId(module, namedModuleId);
}
}
}
});
});
};
const withNamedLazyChunks = (nextConfig = {}) => Object.assign({}, nextConfig, {
webpack: (config, options) => {
config.plugins.push(
mapModuleIds((id, module) => {
if (
id.includes('/global-brand-statement.js')
|| id.includes('signposting/signposting.js')
|| id.includes('reviews-container/index.js')
|| id.includes('why-we-made-this/why-we-made-this.js')
) {
return `lazy-${module.debugId}`;
}
return false;
}),
);
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options);
}
return config;
},
});
In file, using next/dynamic
<LazyHydrate whenVisible style={null} className="col-xs-12">
<GlobalBrandStatement data={globalBrandData} />
</LazyHydrate>
Not sure if this is what you’re after, but I use lazy hydration mixed with webpack plugin and custom next head to preserve the html but strip out below the fold dynamic imported scripts. So I only download the JS and hydrate the component just before the user scrolls into view. Regardless of it the component was used during render - I don’t need the runtime unless a user is going to see it.
Currently in production and has reduced initial page load by 50%. No impact to SEO
Get me on twitter #scriptedAlchemy if you need the implementation, I’ve not yet written a post about it - but I can tell you it’s totally possible to achieve this “download as you scroll” design with very little effort.
import { useState } from "react";
import dynamic from "next/dynamic";
const MyComponent = dynamic(() => import("components/my-component"));
export const PageComponent = () => {
const [downloadComp, setDownloadComp] = useState(false);
return (
<>
<div className="some-class-name">
<button onClick={() => setDownloadComp(true)}>
Download the component
</button>
{downloadComp && <MyComponent />}
</div>
</>
);
};
The above code will download the once you hit the button. If you want it to download if your scroll to position in that case you can use something like react-intersection-observer to set the setDownloadComp. I don't have experience using react-lazy-hydration but I have been using react-intersection-observer and nextjs dynamic import to lazy load components that depends on user scroll.
I have made a library to make this thing simple. And you can benefit with:
Fully HTML page render (Better for SEO)
Only load JS and Hydrate when needed (Better for PageSpeed)
How to use it
import lazyHydrate from 'next-lazy-hydrate';
// Lazy hydrate when scroll into view
const WhyUs = lazyHydrate(() => import('../components/whyus'));
// Lazy hydrate when users hover into the view
const Footer = lazyHydrate(
() => import('../components/footer', { on: ['hover'] })
);
const HomePage = () => {
return (
<div>
<AboveTheFoldComponent />
{/* ----The Fold---- */}
<WhyUs />
<Footer />
</div>
);
};
Read more: https://github.com/thanhlmm/next-lazy-hydrate

NextJS Google Translate Widget

I have a NextJS application and I want to add Google auto translate widget to my app.
So made a function like this:
function googleTranslateElementInit() {
if (!window['google']) {
console.log('script added');
var script = document.createElement('SCRIPT');
script.src =
'//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
document.getElementsByTagName('HEAD')[0].appendChild(script);
}
setTimeout(() => {
console.log('translation loaded');
new window.google.translate.TranslateElement(
{
pageLanguage: 'tr',
includedLanguages: 'ar,en,es,jv,ko,pt,ru,zh-CN,tr',
//layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
//autoDisplay: false,
},
'google_translate_element'
);
}, 500);
}
And I call this function in useEffect(), it loads but when I route to another page it disappers.
When I checked the console I saw translation loaded so setTimeout scope called every time even when I route to another page but translation widget is not appear, only appear when I refresh the page.
How can I solve this?
Thanks to the SILENT's answer: Google no longer support this widget.
So I'm going to configure next-i18next which is a i18n (lightweight translation module with dynamic json storage) for NextJS.
Also, I think the problem with this widget was Google's JS code is attach that widget to DOM itself so it's not attached to VirtualDOM, thats why when I route in app, React checked VirtualDOM and update DOM itself so the widget disappear because it's not on VirtualDOM. (That's just a guess)
Edit: after further testing I found that this code might still be unstable. Be careful if using it in production.
Use the code below inside your custom app and do not forget to put <div id="google_translate_element" /> inside your page or component. Based on this and this answers.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
const MyApp = ({ Component, pageProps }) => {
const { isFallback, events } = useRouter()
const googleTranslateElementInit = () => {
new window.google.translate.TranslateElement({ pageLanguage: 'en' }, 'google_translate_element')
}
useEffect(() => {
const id = 'google-translate-script'
const addScript = () => {
const s = document.createElement('script')
s.setAttribute('src', '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit')
s.setAttribute('id', id)
const q = document.getElementById(id)
if (!q) {
document.body.appendChild(s)
window.googleTranslateElementInit = googleTranslateElementInit
}
}
const removeScript = () => {
const q = document.getElementById(id)
if (q) q.remove()
const w = document.getElementById('google_translate_element')
if (w) w.innerHTML = ''
}
isFallback || addScript()
events.on('routeChangeStart', removeScript)
events.on('routeChangeComplete', addScript)
return () => {
events.off('routeChangeStart', removeScript)
events.off('routeChangeComplete', addScript)
}
}, [])
return <Component {...pageProps} />
}
export default MyApp

How to generate a menu based on the files in the pages directory in Next.js

I am trying to create a menu component that reads the contents of the pages folder at build time. However I haven't had any success. Here is what I have tried:
import path from "path";
import * as ChangeCase from "change-case";
export default class Nav extends React.Component {
render() {
return (
<nav>
{this.props.pages.map((page) => (
<a href={page.link}>{page.name}</a>
))}
</nav>
);
}
async getStaticProps() {
let files = fs.readdirSync("../pages");
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(file);
return stat.isFile();
});
const pages = files.map((file) => {
if (file == "index.js") {
const name = "home";
const link = "/";
} else {
const link = path.parse(file).name;
const name = ChangeCase.camelCase(link);
}
console.log(link, name);
return {
name: name,
link: link,
};
});
return {
props: {
pages: pages,
},
};
}
}
This does not work, the component does not receive the pages prop. I have tried switching to a functional component, returning a promise from getStaticProps(), switching to getServerSideProps(), and including the directory reading code into the render method.
The first two don't work because getStaticProps() and getServerSideProps() never get called unless the component is a page, and including the code in the render method fails because fs is not defined or importable since the code might run on the front end which wouldn't have fs access.
I've also tried adding the code to a getStaticProps() function inside _app.js, with the hopes of pushing the pages to the component via context, but it seems getStaticProps() doesn't get called there either.
I could run the code in the getStaticProps function of the pages that include the menu, but I would have to repeat that for every page. Even if I extract the logic into a module that gets called from the getStaticProps, so something like:
// ...
export async function getStaticProps() {
return {
props: {
pages: MenuMaker.getPages(),
// ...
}
}
}
and then pass the pages to the navigation component inside the page via the Layout component:
export default function Page(props) {
return (
<Layout pages={props.pages}></Layout>
)
}
then that's still a lot of boilerplate to add to each page on the site.
Surely there is a better way... It can't be that there is no way to add static data to the global state at build time, can it? How do I generate a dynamic menu at build time?
I managed to get this working by exporting a function from next.config.js and setting an environment variable that contains the menu structure. I abstracted the menu loading code into it's own file. After seeing the result, I understand better why I was not able to find an example of anyone doing something similar:
The menu is not ordered the way I would like. I could sort it alphabetically, or by the modification date but realistically it almost always needs to be manually sorted in relation to the subject of the pages. I could use an integer, either tacked on to the filename or somewhere in the file (perhaps in a comment line). But in retrospect I think that just hard coding the links in a component is probably the best way after all since it offers much more flexibility and probably isn't going to be much more work even in the very long run.
That being said I am sharing my solution as it is a way to initialize an app wide static state. It's not ideal, you will have to restart the dev server if you wish to recalculate the variables here, which is why I'm still interested in other possible solutions, but it does work. So here it is:
next.config.js
const menu = require("./libraries/menu.js");
module.exports = (phase, { defaultConfig }) => {
return {
// ...
env: {
// ...
menu: menu.get('pages'),
// ...
},
// ...
};
};
libraries/menu.js
const fs = require("fs");
const path = require("path");
const ccase = require("change-case");
module.exports = {
get: (pagePath) => {
if (pagePath.slice(-1) != "/") pagePath += "/";
let files = fs.readdirSync(pagePath);
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(pagePath + file);
return stat.isFile();
});
return files.map((file) => {
if (file == "index.js") {
return {
name: "Home";
link: "/";
};
} else {
link = path.parse(file).name;
return {
link: link;
name: ccase.capitalCase(link);
};
}
});
},
};
Then the actual menu is generated from the environment variable in a component that can be included in the layout:
components/nav.js
import Link from "next/link";
export default class Nav extends React.Component {
render() {
return (
<nav>
{process.env.menu.map((item) => (
<Link key={item.link} href={item.link}>
<a href={item.link}>
{item.name}
</a>
</Link>
))}
</nav>
);
}
}
You can try this:
const fg = require('fast-glob');
const pages = await fg(['pages/**/*.js'], { dot: true });

How to implement GTM with reactjs

I am trying to implement GTM with reactjs. I have used react-google-tag-manager but it did not solve the purpose.
Somehow, the data layer needs to be in a particular format and also the needs to be right below the tag, but it is only one of them that i can achieve at a time.
I tried placing the code directly in template.html and call the function from the component i wanted, but that didn't work.
import React from 'react';
import gtmParts from 'react-google-tag-manager';
class GoogleTagManager extends React.Component {
componentDidMount() {
const dataLayerName = this.props.dataLayerName || 'dataLayer';
const scriptId = this.props.scriptId || 'react-google-tag-manager-gtm';
if (!window[dataLayerName]) {
const gtmScriptNode = document.getElementById(scriptId);
eval(gtmScriptNode.textContent);
}
}
render() {
const gtm = gtmParts({
id: this.props.gtmId,
sourcegroup: this.props.gtmGroupname,
sourceid:this.props.gtmSource,
age:this.props.age,
mtongue:this.props.gtmMtongue,
city:this.props.city,
});
return (
<div>
<div>{gtm.noScriptAsReact()}</div>
<div id={this.props.scriptId || 'react-google-tag-manager-gtm'}>
{gtm.scriptAsReact()}
</div>
</div>
);
}
}
export default GoogleTagManager;
I am pushing parameters in DataLayer and on checking on google tag assistant addon, whole the datalyer is empty.
I had this issue yesterday and to solve it, I had to put all the properties that I'm trying to record under the additionalEvents property. Something like this:
const gtm = gtmParts({
id: this.props.gtmId,
additionalEvents: {
sourcegroup: this.props.gtmGroupname,
sourceid:this.props.gtmSource,
age:this.props.age,
mtongue:this.props.gtmMtongue,
city:this.props.city
}
})
And also avoid using eval() since this is a dangerous pratique. Update your code like this:
if (!window[dataLayerName]) {
const script = document.createElement("script")
const gtmScriptNode = document.getElementById(scriptId)
const scriptText = document.createTextNode(gtmScriptNode.textContent)
script.appendChild(scriptText)
document.head.appendChild(script)
}

Resources