Next.js Discord

Discord Forum

Loader screen showing up multiple times.

Unanswered
Shayokh posted this in #help-forum
Open in Discord
Avatar
I am trying to build a portfolio with multiple pages. The very first page is a loader screen with some text. Right after the loader screen finishes, the landing page shows up.

The issue is, every time the user visits a page within the portfolio and comes back to the / landing page, the loader screen reappears. I only want it to appear when the user visits the portfolio for the very first time.

Here is the code -
"use client";

import { useState, useRef } from "react";
import Hero from "./Hero";
import Loader from "./Loader";
import Writer from "./Writer";
import Years from "./Years";
import HorizontalBooks from "./HorizontalBooks";
import LawText from "./LawText";
import Gallery from "./Gallery";
import Footer from "./Footer";

export default function Page() {
  const loaderComplete = useRef(false);

  const handleComplete = () => {
    loaderComplete.current = true;
  };

  return (
    <main>
      {!loaderComplete.current && <Loader onComplete={handleComplete} />}

      <Hero />
      <Writer />
      <div className="hidden md:block">
        <HorizontalBooks />
      </div>
      <Years />
      <LawText />
      <Gallery />
      <Footer />
    </main>
  );
}


Here is a screen record of the issue - https://streamable.com/fcyzg9
Stackbltiz - https://stackblitz.com/edit/github-sq9mef9f?file=app%2Fpage.tsx

14 Replies

Avatar
useRef is not persistent on site refresh, you should save loaderComplete value on local storage or cookie
Avatar
@chisto useRef is not persistent on site refresh, you should save loaderComplete value on local storage or cookie
Avatar
Dutch Smoushond
this can also be solved using a global state right? make the global loading state false after first load and then don't show loader when the global state is false
but i don't think that's the optimal approach
Avatar
I will try it out and update the results here.
Avatar
@Alfonsus Ardani Click to see attachment
Avatar
I actually tried using the useState in a older implementation
It sort of worked but the entire layout shift right after loader is finished made the landing page a little janky on mobile devices (sometimes on pc too)
Here is the working older implementation
page.tsx
"use client";

import { useState } from "react";
import Hero from "./Hero";
import Loader from "./Loader";
import Writer from "./Writer";
import Years from "./Years";
import HorizontalBooks from "./HorizontalBooks";
import LawText from "./LawText";
import Gallery from "./Gallery";
import Footer from "./Footer";

export default function Page() {
  const [loaderComplete, setLoaderComplete] = useState(false);

  const handleComplete = () => {
    setLoaderComplete(true);
  };

  return (
    <main>
      <Loader onComplete={handleComplete} />
      {loaderComplete && (
        <>
          <Hero />
          <Writer />
          <div className="hidden md:block">
            <HorizontalBooks />
          </div>
          <Years />
          <LawText />
          {/* <Gallery /> */}
          <Footer />
        </>
      )}
    </main>
  );
}
loader.tsx
"use client";

import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { useRef } from "react";

gsap.registerPlugin(useGSAP);

export default function Loader({ onComplete }: { onComplete: () => void }) {
  const sentences = [
    "Who am I?",
    "An agent of justice.",
    "An artist of words.",
    "A voice for the nation.",
  ];

  const loaderRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    const timeline = gsap.timeline({
      onComplete: onComplete,
    });

    sentences.forEach((_, index) => {
      timeline
        .to(`#text-${index}`, { opacity: 1, display: "block", duration: 1 })
        .to(`#text-${index}`, {
          opacity: 0,
          display: "none",

          duration: 1,
          delay: 1.2,
        });
    });

    timeline.to("#loader", { opacity: 0 }).to("#loader", { display: "none" });
  }, []);

  return (
    <div
      className="flex justify-center items-center h-full w-full fixed top-0 z-30 bg-black text-white"
      id="loader"
      ref={loaderRef}
    >
      {sentences.map((sentence, index) => (
        <h1
          key={index}
          id={`text-${index}`}
          className="text-xl md:text-2xl lg:text-3xl font-nb opacity-0 hidden"
        >
          {sentence}
        </h1>
      ))}
    </div>
  );
}