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.
Related
I am using hook router 1.2.5 and I have a very simple home page as below:
import { useRoutes } from "hookrouter";
import React from "react";
import Nav from "./pages/Nav";
import AboutPage from "./pages/About";
const HomePage = () => {
const routeResult = useRoutes({
"/about": () => <AboutPage />
});
return (
<div fluid>
<div xs={3} md={1} lg={1} className="nav-container">
<Nav />
</div>
<div xs={9} md={11} lg={11}>
{routeResult || <AboutPage />}
</div>
</div>
);
};
export default HomePage;
But when I run lint, I see below warnings show up.
8:10 warning Component definition is missing display name react/display-name
8:10 warning Declare only one React component per file react/no-multi-comp
I know I can disable these eslint warnings. But I would like to know how to fix them. For example, I don't have another component in my file. So why would it show react/no-multi-comp warning, or did I miss something? Any helps are appreciated.
UPDATE
I was able to fix react/display-name by replacing the arrow function as below:
const routeResult = useRoutes({
"/about"() {
return <AboutPage />;
}
});
I'm working on a legacy react-app, hence lot of pieces cannot be reasoned about but simply accepted.
So, I have a couple of components that load a lot of dependencies and are obviously not important for the first render. Hence, I tried the following:
const HeavyComp = lazy(() => import("HeavyComponent.jsx");
function Home() {
return <div>
<HeavyComp />
</div>
}
As a result of this, HeavyComponent is loaded as part of main bundle and but is only visible after that component is loaded. This helps by breaking scripting time but FCP is still far away.
So, I tried the following:
function Home() {
const [ heavyComponent. setHeavyComponent ] = useState(null);
useEffect(() => {
setHeavyComponent(
lazy(() => import("HeavyComponent.jsx")
);
}, []);
return <div>
{
heavyComponent && <heavyComponent />
}
</div>
}
I thought this'd help but same as before, FCP was still delayed until heavyComponent was downloaded, parsed and rendered. So my only option was to make it async using setTimeout or better requestIdleCallback.
Is this the best solution or is there something better?
Assuming that with FCP you are referring to "first content paint". The best option is to use the Suspense component. With it, you can add a fallback loader component (<Spinner /> in this example).
import { Suspense, lazy } from 'react';
const HeavyComp = lazy(() => import("HeavyComponent.jsx");
function Home() {
return <div>
<Suspense fallback={<Spinner />}>
<HeavyComp />
</Suspense>
</div>
}
React concurrent-mode documentation
I want each of my pages to have different loading animations when loading. How can i achieve this?
It is not possible to put the loading component on the page component like this:
//Page component
Page.Loader = SomeLoaderComponent
//_app.tsx
const Loader = Component.Loader || DefaultLoader;
This will not work because "Component(the page)" isnt loaded/rendered yet.
I have also tried dynamic import with next.js, so that i can import the correct page based on the url, and then get the correct loader. My initial plan was to add the Loader to the page component, as shown at the first line in the code above. That does not work because I have to give an explicit path.
const getLoader = (pagePath: string): ComponentType => {
const Loader = dynamic(() => import(pagePath));
return Page.Loader;
};
This is stated in the Next.js docs:
So the question is: How can I get a custom loader per page?
You can use Suspense and lazy to accomplish your task.
lets say you have ComponentA.js as follows
const ComponentA = () => {
return <div>Helloooo</div>
}
You can create another component WrapperA.js file
import React, { Suspense } from 'react';
const WrapperA = React.lazy(() => import('./ComponentA'));
function WrapperA() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<ComponentA />
</Suspense>
</div>
);
}
in the place of <div>Loading...</div> you can have any loader component you like. and export WrapperA to your routes or tab as needed.
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.
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])
}