How to use vanta with nextjs? - javascript

I am trying to use vanta with next.js, following this guide. It works completely fine with the Net Effect, however, when I try to use the Globe Effect, I get
[VANTA] Init error TypeError: r.Geometry is not a constructor
at h.onInit (vanta.globe.min.js:1)
at h.init (vanta.globe.min.js:1)
at new r.VantaBase (vanta.globe.min.js:1)
at new h (vanta.globe.min.js:1)
at r.<computed> (vanta.globe.min.js:1)
I have isolated Vanta into an Background Component
//Background.js
import { useState, useRef, useEffect } from "react";
import NET from "vanta/dist/vanta.globe.min"
import * as THREE from "three";
export default function Background({ width, height, children }) {
const [vantaEffect, setVantaEffect] = useState(0);
const vantaRef = useRef(null);
useEffect(() => {
if (!vantaEffect) {
setVantaEffect(
NET({
THREE,
el: vantaRef.current,
})
);
}
return () => {
if (vantaEffect) vantaEffect.destroy();
};
}, [vantaEffect]);
return (
<div ref={vantaRef}>{children}</div>
)
}
And added the THREE script into my _app.js
import '../styles/globals.css'
import Head from "next/head";
import Navbar from "../components/Navbar";
import { useEffect } from "react";
export default function App({ Component, pageProps }) {
useEffect(() => {
const threeScript = document.createElement("script");
threeScript.setAttribute("id", "threeScript");
threeScript.setAttribute(
"src",
"https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"
);
document.getElementsByTagName("head")[0].appendChild(threeScript);
return () => {
if (threeScript) {
threeScript.remove();
}
};
}, []);
return (
<>
<Head>
<title>BrainStorm Tutoring</title>
</Head>
<Navbar />
<Component {...pageProps} />
</>
)
}
and used it like so
//index
import Background from "../components/Background";
export default function Home() {
return (
<Background height="400" width="400">
<h1 className="text-white text-8xl text-left p-36">Fish Bowl</h1>
</Background >
)
}
Is it something wrong with THREE, or is it that next.js can't support vanta?

I have that issue with Halo, so i think the THREE object was not available or was not defined in the HALO.js file.
So i go to the official github repo of Vanta and take the source of Halo and Net (the tutorial effect) file, and i found constructor was missing in the Halo file. So i take the one of Net and put in the Halo file.
constructor(userOptions) {
THREE = userOptions.THREE || THREE;
super(userOptions);
}
Then i import my custom Halo file for the effect and it works.

I was playing around with this and found that, if I keep the Three.js version to 122. I don't get the error. Apparently any version after that has a breaking change.

Related

Loading page on nextjs 13

Hi im trying to get a loading page to show while website is taking the time to load. as it quite a large website I thought a loading screen would provide the best possible user experience however I cannot seem to figure out how to get it to work on nextjs 13. I have created a simple functional component that says loading... and have imported it directly into my layout.jsx folder.
I am using the app directory method which is quite new and im also new at nextjs so im a little lost ^^
I imagine I might need to set state at some point but I cant seem to figure out when and where to do it
any advice would be great.
thanks
import "./globals.css";
import React, { useState, useEffect } from "react";
import Loading from "../components/loading/loading";
const Layout = ({ children, dataLoaded }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (dataLoaded) {
setLoading(false);
}
}, [dataLoaded]);
return (
<body className="app {oswald.className}">
{loading && <Loading />}
{children}
</body>
);
};
export default Layout;
.
.
.
Attempt 1 -
After following one of the answers below it does not seem like my loading page is showing up at all. and no errors showing up.
my layout is as follows
layout.jsx
import "./globals.css";
import { Suspense } from "react";
import Loading from "../components/loading/loading";
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Suspense fallback={<Loading />}>{children}</Suspense>
</body>
</html>
);
}
LoadingPage.js
const LoadingPage = () => {
return (
<div className="loading w-screen h-screen bg-red-100">
<p>Loading...</p>
</div>
);
};
export default LoadingPage;
Loading.js
import LoadingPage from "#/components/loading/loading";
export default function Loading() {
return <LoadingPage />;
}
In NextJS 13, there's actually a default way to handle loading states within pages. You can declare a loading.tsx file in your /app directory, with this content:
export default function Loading() {
return <Loading />
}
Then, inside your Layout, you can wrap your page with a Suspense tag, like this:
<Layout>
<Navbar>
...
<Suspense fallback={<Loading/>}>
<Page/>
</Suspense>
</Layout>
Your loading state will be automatically handled upon navigation.

Next.js renders element twice

On the first image you can see next.js rendered this element twice
I used tables and thought that it is because of them but then I tried to remove tables and put jut and it still renders twice so I don't know what it can be.
Next.js does not renders only that element but the first from this object
const Sections = {
1: Locations,
0: Departments, // here it will render this one twice
2: Managers,
3: JobTitles,
};
Maybe it has something to do with useState and my statemanagment in this code below
Component that renders twice.
const Locations = () => {
return <div>hdjsad</div>;
};
// Tab Sections
import Locations from ''
import Departments from ''
import Managers from ''
import JobTitles from ''
import Icons from "../../Icons";
import TabItem from "./TabItem";
const tabs_text = ["Locations", "Departments", "Managers", "Job Titles"];
const Sections = {
0: Locations, // THIS IS THE COMPONENT WHICH RENDERS TWICE
1: Departments,
2: Managers,
3: JobTitles,
};
const SettingsTab = () => {
const [active, setActive] = useState<number>(0);
const select = useCallback((id: number) => {
return () => setActive(id);
}, []);
const ActiveSection = useMemo(() => Sections[active], [active]);
return (
<section className={"mb-[24px]"}>
<header
className={"w-full flex items-center mb-[34px] pl-[24px] pr-[12px]"}
>
<div className={"flex space-x-[8px] !mb-0 overflow-x-scroll"}>
{tabs_text.map((tab_text, i) => {
return (
<div onClick={select(i)} key={i}>
<TabItem active={+active === i}>{tab_text}</TabItem>
</div>
);
})}
</div>
<ImportLocationsAndFilter />
</header>
<ActiveSection />
</section>
);
};
APP.js
import { AppProps } from "next/app";
import "antd/dist/antd.css";
import "../styles/global.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
I can't comment yet so I'll do it here. I know react says in the official docs to never rely on UseMemo or Use callback for functionality. It says you should create your application so it works without them, and then add them for performance reasons. What would happen if you took the useMemo out and put
ActiveSelection = Selections[active]
I don't think it'll fix your problem but it might give you more insight into what's causing it.
I just imported my tabs dynamically and set SSR: false.
It has to do something with next.js hydration.
https://nextjs.org/docs/advanced-features/dynamic-import
dynamic(
() => import(""),
{
ssr: false,
}
);
It's strange behaviour / bug related to next.js ssr to fix it wrap your Component in a div like this:
function MyApp({ Component, pageProps }: AppProps) {
return <div id=#root><Component {...pageProps} /></div>;
}

Is there a way to import a function using Next.js dynamic import? react-component-export-image issues with Next.js ssr

I was getting the 'window is not defined' error when importing react-component-export-image so I used a dynamic import to get around that. I don't get that error anymore but now I get 'exportComponentAsPNG(componentRef) is not a function'. Is there a better way to deal with the 'window is not defined' error or a way to use the function I am importing dynamically? If not, is there a different npm library that works to generate an image from a react component?
import React, { useRef } from 'react'
// import { exportComponentAsPNG } from 'react-component-export-image' *This gave window not defined error so I used dynamic import*
import dynamic from 'next/dynamic'
import ProductCard from '../ProductCard/ProductCard.component'
import Button from '../Button/Button.component'
const { exportComponentAsPNG } = dynamic(
() => import('react-component-export-image'),
{
ssr: false
}
)
const Plaque = () => {
const componentRef = useRef()
// eslint-disable-next-line react/display-name
const ComponentToPrint = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
<ProductCard />
</div>
)
})
return (
<ComponentToPrint ref={componentRef} />
<button onClick={() => exportComponentAsPNG(componentRef)}> // "Error: exportComponentAsPNG is not a function"
Export As PNG
</button>
)
}
export default Plaque
next/dynamic is used to dynamically import React components, not regular JavaScript functions or libraries.
For that, you can use a regular dynamic import on exportComponentAsPNG inside the onClick callback.
<button onClick={async () => {
const { exportComponentAsPNG } = await import('react-component-export-image')
exportComponentAsPNG(componentRef)
}}>
The exportComponentAsPNG function needs access to window which is undefined with server side rendering. I was able to fix the issue by dynamically importing the Plaque component that used exportComponentAsPNG to the page where it is called with sever side rendering set to 'false'.
import dynamic from 'next/dynamic'
const Plaque = dynamic(() => import('../compnonents/Plaque'), {
ssr: false
})
const Page = () => {
return <Plaque />
}
export default Page
Now that the component is no longer using SSR I was able to import and use the function normally.
import { exportComponentAsPNG } from 'react-component-export-image'
Here you can find the documentation for the library: https://www.npmjs.com/package/react-component-export-image

usePreventScroll causes useLayoutEffect warning in Nextjs

I'm learning Next.js and I'm trying to integrate the #react-aria/overlays package in my project. I have a layout component, where I'm simply invoking the usePreventScroll method like this:
usePreventScroll({
isDisabled: true
});
This layout component is used in my _app.js.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import * as gtag from '../lib/gtag'
import 'styles/vendor.scss';
import 'styles/globals.scss';
import Layout from 'components/layout';
import { SSRProvider } from '#react-aria/ssr';
const App = ({ Component, pageProps }) => {
return (
<SSRProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</SSRProvider>
)
}
export default App;
When going to my browser and loading a page, it gives me the following error:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
at Layout (/home/bas/projects/test-website/build/server/pages/_app.js:718:3)
at div
at $c5f9596976ab8bd94c5879001549a3e$var$OverlayContainerDOM (/home/bas/projects/test-website/node_modules/#react-aria/overlays/dist/main.js:864:7)
at ModalProvider (/home/bas/projects/test-website/node_modules/#react-aria/overlays/dist/main.js:810:5)
at OverlayProvider
at SSRProvider (/home/bas/projects/test-website/node_modules/#react-aria/ssr/dist/main.js:33:13)
at UIContextProvider (/home/bas/projects/test-website/build/server/pages/_app.js:1144:74)
at ManagedUIContext (/home/bas/projects/test-website/build/server/pages/_app.js:1105:3)
at App (/home/bas/projects/test-website/build/server/pages/_app.js:5171:3)
at AppContainer (/home/bas/projects/test-website/node_modules/next/dist/next-server/server/render.js:23:748)
What's the problem here and how would I be able to solve it?
I tried wrapping the the Layout component in the packages <SSRProvider>.
You can dynamically load the component and disable SSR:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The code example has been taken from the NextJS docs. If that's not your thing, you can call the hook or render the component as long as processs.browser is true.
Next js is computes your 1st page on server. so it does not understand browser scroll or localstorage or other browser api.
you can add a check in your code block if window object is present or execution is running in server and then execute usePreventDefault.
import {useIsSSR} from '#react-aria/ssr';
function Layout() {
let isSSR = useIsSSR();
useEffect(() => {
!isSSR && usePreventScroll({ ... })
}, [isSSR])
}

How can I customize the style of a React component shared between lazy-loaded pages?

I'm building a React application and I started using CRA. I configured the routes of the app using React Router. Pages components are lazy-loaded.
There are 2 pages: Home and About.
...
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
...
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Switch>
</Suspense>
...
Each page uses the Button component below.
import React from 'react';
import styles from './Button.module.scss';
const Button = ({ children, className = '' }) => (
<button className={`${styles.btn} ${className}`}>{children}</button>
);
export default Button;
The Button.module.scss file just sets the background color of the button to red.
.btn {
background: red;
}
The Button component accepts a className prop which is then added to the rendered button. This is because I want to give freedom to the consumer of the component. For example, in some pages margins could be needed or the background should be yellow instead of red.
To make it simple, I just want to have a different background color for the Button based on the current page, so that:
Home page => Blue button
About page => Yellow button
Each page is defined as below:
import React from 'react';
import Button from './Button';
import styles from './[PageName].module.scss';
const [PageName] = () => (
<div>
<h1>[PageName]</h1>
<Button className={styles.pageBtn}>[ExpectedColor]</Button>
</div>
);
export default [PageName];
where [PageName] is the name of the page and [ExpectedColor] is the corresponding expected color based on the above bullet list (blue or yellow).
The imported SCSS module, exports a class .pageBtn which sets the background property to the desired color.
Note: I could use a prop on the Button component which defines the variant to display (Blue/Yellow) and based on that prop add a class defined in the SCSS file. I don't want to do that since the change could be something that doesn't belong to a variant (e.g. margin-top).
The problem
If I run the application using yarn start, the application works fine. However, if I build the application (yarn build) and then I start serving the application (e.g. using serve -s build), the behavior is different and the application doesn't work as expected.
When the Home page is loaded, the button is correctly shown with a blue background. Inspecting the loaded CSS chunk, it contains:
.Button_btn__2cUFR {
background: red
}
.Home_pageBtn__nnyWK {
background: blue
}
That's fine. Then I click on the navigation link to open the About page. Even in this case, the button is shown correctly with a yellow background. Inspecting the loaded CSS chunk, it contains:
.Button_btn__2cUFR {
background: red
}
.About_pageBtn__3jjV7 {
background: yellow
}
When I go back to the Home page, the button is now displayed with a red background instead of yellow. That's because the About page has loaded the CSS above which defines again the Button_btn__2cUFR class. Since the class is now after the Home_pageBtn__nnyWK class definition, the button is displayed as red.
Note: the Button component is not exported on the common chunk because its size is too small. Having that in a common chunk could solve the problem. However, my question is about small shared components.
Solutions
I have thought to 2 solutions which, however, I don't like too much:
Increase selectors specificity
The classes specified in the [PageName].module.scss could be defined as:
.pageBtn.pageBtn {
background: [color];
}
This will increase the selector specificity and will override the default Button_btn__2cUFR class. However, each page chunk will include the shared components in case the component is quite small (less than 30kb). Also, the consumer of the component has to know that trick.
Eject and configure webpack
Ejecting the app (or using something like react-app-rewired) would allow specifying the minimum size for common chunk using webpack. However, that's not what I would like for all the components.
To summarize, the question is: what is the correct working way of overriding styles of shared components when using lazy-loaded routes?
You can use the following logic with config file for any pages. Also, You can send config data from remote server (req/res API) and handle with redux.
See Demo: CodeSandBox
create components directory and create files like below:
src
|---components
|---Button
| |---Button.jsx
| |---Button.module.css
Button Component:
// Button.jsx
import React from "react";
import styles from "./Button.module.css";
const Button = props => {
const { children, className, ...otherProps } = props;
return (
<button className={styles[`${className}`]} {...otherProps}>
{children}
</button>
);
};
export default Button;
...
// Button.module.css
.Home_btn {
background: red;
}
.About_btn {
background: blue;
}
create utils directory and create AppUtils.js file:
This file handle config files of pages and return new object
class AppUtils {
static setRoutes(config) {
let routes = [...config.routes];
if (config.settings) {
routes = routes.map(route => {
return {
...route,
settings: { ...config.settings, ...route.settings }
};
});
}
return [...routes];
}
static generateRoutesFromConfigs(configs) {
let allRoutes = [];
configs.forEach(config => {
allRoutes = [...allRoutes, ...this.setRoutes(config)];
});
return allRoutes;
}
}
export default AppUtils;
create app-configs directory and create routesConfig.jsx file:
This file lists and organizes routes.
import React from "react";
import AppUtils from "../utils/AppUtils";
import { pagesConfig } from "../pages/pagesConfig";
const routeConfigs = [...pagesConfig];
const routes = [
...AppUtils.generateRoutesFromConfigs(routeConfigs),
{
component: () => <h1>404 page not found</h1>
}
];
export default routes;
Modify index.js and App.js files to:
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
rootElement
);
...
react-router-config: Static route configuration helpers for React
Router.
// App.js
import React, { Suspense } from "react";
import { Switch, Link } from "react-router-dom";
import { renderRoutes } from "react-router-config";
import routes from "./app-configs/routesConfig";
import "./styles.css";
export default function App() {
return (
<div className="App">
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<Suspense fallback={<h1>loading....</h1>}>
<Switch>{renderRoutes(routes)}</Switch>
</Suspense>
</div>
);
}
create pages directory and create files and subdirectory like below:
src
|---pages
|---about
| |---AboutPage.jsx
| |---AboutPageConfig.jsx
|
|---home
|---HomePage.jsx
|---HomePageConfig.jsx
|
|---pagesConfig.js
About Page files:
// AboutPage.jsx
import React from "react";
import Button from "../../components/Button/Button";
const AboutPage = props => {
const btnClass = props.route.settings.layout.config.buttonClass;
return (
<>
<h1>about page</h1>
<Button className={btnClass}>about button</Button>
</>
);
};
export default AboutPage;
...
// AboutPageConfig.jsx
import React from "react";
export const AboutPageConfig = {
settings: {
layout: {
config: {
buttonClass: "About_btn"
}
}
},
routes: [
{
path: "/about",
exact: true,
component: React.lazy(() => import("./AboutPage"))
}
]
};
Home Page files:
// HomePage.jsx
import React from "react";
import Button from "../../components/Button/Button";
const HomePage = props => {
const btnClass = props.route.settings.layout.config.buttonClass;
return (
<>
<h1>home page</h1>
<Button className={btnClass}>home button</Button>
</>
);
};
export default HomePage;
...
// HomePageConfig.jsx
import React from "react";
export const HomePageConfig = {
settings: {
layout: {
config: {
buttonClass: "Home_btn"
}
}
},
routes: [
{
path: "/",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
...
// pagesConfig.js
import { HomePageConfig } from "./home/HomePageConfig";
import { AboutPageConfig } from "./about/AboutPageConfig";
export const pagesConfig = [HomePageConfig, AboutPageConfig];
Edited section:
With HOC Maybe this way: CodeSandBox
create hoc dir and withPage.jsx file:
src
|---hoc
|---withPage.jsx
...
// withPage.jsx
import React, { useEffect, useState } from "react";
export function withPage(Component, path) {
function loadComponentFromPath(path, setStyles) {
import(path).then(component => setStyles(component.default));
}
return function(props) {
const [styles, setStyles] = useState();
useEffect(() => {
loadComponentFromPath(`../pages/${path}`, setStyles);
}, []);
return <Component {...props} styles={styles} />;
};
}
And then pages like below:
src
|---pages
|---about
| |---About.jsx
| |---About.module.css
|
|---home
|---Home.jsx
|---Home.module.css
About.jsx file:
// About.jsx
import React from "react";
import { withPage } from "../../hoc/withPage";
const About = props => {
const {styles} = props;
return (
<button className={styles && styles.AboutBtn}>About</button>
);
};
export default withPage(About, "about/About.module.css");
About.module.css file:
// About.module.css
.AboutBtn {
background: yellow;
}
Home.jsx file:
// Home.jsx
import React from "react";
import { withPage } from "../../hoc/withPage";
const Home = props => {
const { styles } = props;
return <button className={styles && styles.HomeBtn}>Home</button>;
};
export default withPage(Home, "home/Home.module.css");
Home.module.css file:
// Home.module.css
.HomeBtn {
background: red;
}
I would suggest instead of adding both the default styles and the consumer styles, use the consumer's styles over yours and use your as a callback if not supplied. The consumer can still compose your defaults with the composes keyword.
Button.js
import React from 'react';
import styles from './Button.module.scss';
const Button = ({ children, className}) => (
<button className={className ?? styles.btn}>{children}</button>
);
export default Button;
SomePage.module.scss
.pageBtn {
// First some defaults
composes: btn from './Button.module.scss';
// And override some of the defautls here
background: yellow;
}
If you wish, use sass #extends or #mixin instead
EDIT: Haven't tested it, but could it be that just by using composes webpack will make sure to bundle the defaults only once? Thus you're no longer needed to change your Button.js code with the ??
Solution 1
I know this is very obvious, but would work anyway:
Set !important on your overwriting css rules, thus bypassing specificity:
[PageName].module.scss:
.btn {
color: yellow !important;
}
However, most of the strict devs I know would avoid this keyword at all cost.
Why ?
Because when you start to have a lot of !important your css is a nightmare to debug. If you start writing !important rules with higher specificity, you know you have gone too far
It is only meant for corner-cases like yours, you might as well use it.
Solution 2
fix CRA config to enforce style tags order.
It is open-source after all :)
You can give your input on this bug here (upvote might give it more visibility):
https://github.com/facebook/create-react-app/issues/7190
Solution 3 (Update)
You could create a SCSS mixin in a new customButton.scss file, to generate css rules with higher specificity:
// customButton.scss
#mixin customBtn() {
:global {
.customBtn.override {
#content;
}
}
}
We will use two static class names (using the :global selector), because that way their name won't change based on where they are imported from.
Now use that mixin in your pages' SCSS:
// [pageName].module.scss
#import 'customButton.scss';
#include customBtn {
color: yellow;
}
css output should be:
.customBtn.override {
// put everything you want to customize here
color: yellow;
}
In Button.jsx: apply both class names to your button in addition to styles.btn:
// Button.jsx
const Button = ({ children, className = '' }) => (
<button className={`${styles.btn} customBtn override ${className}`}>
{children}
</button>
);
(Note that these are not referenced through the styles object, but the classname directly)
The main drawback is these are not dynamic class names, so you have to watch out to avoid conflicts yourself like we use to do before.
But I think it should do the trick

Categories

Resources