Skip to content

useEffect not working in root loading.tsx on initial loading #41972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
lukemorales opened this issue Oct 27, 2022 · 10 comments
Closed
1 task done

useEffect not working in root loading.tsx on initial loading #41972

lukemorales opened this issue Oct 27, 2022 · 10 comments
Labels
bug Issue was opened via the bug report template. locked

Comments

@lukemorales
Copy link

lukemorales commented Oct 27, 2022

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.6.0: Wed Aug 10 14:28:23 PDT 2022; root:xnu-8020.141.5~2/RELEASE_ARM64_T6000
Binaries:
  Node: 16.14.2
  npm: 7.24.2
  Yarn: 1.22.19
  pnpm: 7.13.3
Relevant packages:
  next: 13.0.1-canary.0
  eslint-config-next: 13.0.0
  react: 18.2.0
  react-dom: 18.2.0

What browser are you using? (if relevant)

Arc Browser

How are you deploying your application? (if relevant)

No response

Describe the Bug

loading.tsx does not throw any errors if made a Client Component with 'use client' declarative, however, useEffect does not run on Loading components.

Expected Behavior

useEffect is execute.

Link to reproduction

https://stackblitz.com/edit/vercel-next-js-duighx?file=app/loading.tsx

To Reproduce

Create a loading file in /app directory and use the following code:

"use client";

import { useState, useEffect } from "react";

export default function LoadingSpinner() {
  const [dots, setDots] = useState(".");

  useEffect(() => {
    console.log("running effect...");
    const interval = setInterval(() => {
      console.log("running...");
      setDots((prevDots) => prevDots + ".");
    }, 100);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <p>Loading{dots}</p>;
}
@lukemorales lukemorales added the bug Issue was opened via the bug report template. label Oct 27, 2022
@The-Code-Monkey
Copy link

@lukemorales i have the same issue apart from my data isnt being fetched in my case. useEffect not getting called in a client side component.

@lukemorales lukemorales changed the title useEffect not working in loading.tsx useEffect not working in root loading.tsx Oct 28, 2022
@lukemorales lukemorales changed the title useEffect not working in root loading.tsx useEffect not working in root loading.tsx on initial loading Oct 28, 2022
@lukemorales
Copy link
Author

Just changed the title because I was able to see state updates happening on nested routes, and the root loading also updating if my App entry point was a nested route then navigating back to the / path. But on first load, the root loading.tsx is not behaving like a client component

@JasperAlexander
Copy link

useEffect is also not working in a client component that has a server component as child, imported from a server component.

@mgmolisani
Copy link

Similarly, I was just playing around with loading.tsx as a client component rendering a <progress> with a progress variable intended to update in setInterval setup in an effect. It fails to run at all on the client. Additionally, even console.log in the function body does not run (i.e. the component is only run on the server). Toggling the suspense boundary in React dev tools triggers the correct behavior (i.e. it finally client renders).

@mgmolisani
Copy link

Might have found some related content.

#44249

After reading that post, it seems to suggest that there is not enough JS loaded to client render/hydrate the app and this is a result of the page still streaming in with that runtime. However, we can clearly see the layout.tsx files have no issue being run as a client component and wraps this component so this seems like an issue specifically with how loading.tsx is rendered.

So I guess the follow up is either can this behavior be avoided like layout.tsx or is there a need for a new or changed API to account for this RSC only first load.

If there is a way to avoid it, then let’s implement it so we can seamlessly use React features when the loader is marked “use client”. It seems reasonable to expect it to run SSR and rehydrate on any suspension.

If we can’t avoid it, maybe we need a new API to signal developers that there is at first a render that cannot fully take advantage of the client and then subsequent loads can leverage client only. This might be similar to “error.tsx” vs “global-error.tsx” to create a server only loader. Or simply prohibit client loaders (which seems aggressively opinionated). Both options seems bad from either an implementation side or a DX side.

@ekatzenstein
Copy link

ekatzenstein commented Oct 23, 2024

Given the radio silence from Vercel, this ugly hack is working for me. For ONLY the loading.tsx file, use lottie-web instead of lottie-react. Refs and useEffects NEVER fire, so you have to do it all synchronously with vanilla javascript. Hope this helps. Note that the useEffect is only here to clear the loading component for hot reloads (and possibly other prod cases where the component re-renders).

loading.tsx

"use client";

import LoadingSpinner from "@/app/components/Loading/Infinite";

const Loading = () => {
  return (
    <div
      style={{
        display: "flex",
        height: "100%",
        width: "100%",
        minHeight: "100svh",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <LoadingSpinner />
    </div>
  );
};

export default Loading;

@/app/components/Loading/Infinite

"use client";
import lottie from "lottie-web";
import styled from "styled-components";
import animationData from "./lottie/index.json";
import { useEffect } from "react";

const LoadingSpinnerContainer = styled.div`
  width: 100%;
  height: 100%;
  max-width: 3rem;
  max-height: 3rem;
  position: relative;
  z-index: 100;
`;

function LoadingSpinner() {
  const container = document.createElement("div");
  container.style.width = "50px";
  container.style.height = "50px";
  document.getElementById("loading-container")?.appendChild(container);
  lottie.loadAnimation({
    container,
    renderer: "svg",
    loop: true,
    autoplay: true,
    animationData: animationData,
  });

  useEffect(() => {
    return () => {
      lottie.destroy();
      document.getElementById("loading-container")?.removeChild(container);
    };
  }, []);

  return (
    <LoadingSpinnerContainer>
      <div
        id="lottie-container"
        style={{ width: "100%", height:"100%" }}
        ref={(el) => {
          if (el) {
            lottie.loadAnimation({
              container: el,
              renderer: "svg",
              loop: true,
              autoplay: true,
              animationData: animationData,
            });
          }
        }}
      />
    </LoadingSpinnerContainer>
  );
}

export default LoadingSpinner;

@hson194
Copy link

hson194 commented Nov 2, 2024

Just changed the title because I was able to see state updates happening on nested routes, and the root loading also updating if my App entry point was a nested route then navigating back to the / path. But on first load, the root loading.tsx is not behaving like a client component

It happens in Next v14 also

@ztanner
Copy link
Member

ztanner commented Jan 8, 2025

Hey folks, sorry for the delay here. In the case of loading, React is choosing not to hydrate here because it'd be wasting CPU & memory cycles hydrating something that should be removed as soon as possible. It's better for React to spend that time on more important work such as the incoming UI. This is already a problem today with main-thread animations that slow down navigations, to the point where React has implemented mitigations to prevent these resource intensive operations from getting in the way (eg: facebook/react#31828)

For something like implementing a loading spinner, we suggest using CSS animations that work off thread rather than relying on JS. In most cases, by the time the user downloaded the necessary JS to power the loading state (which would have been frozen up until hydration anyway), the loading state should have finished.

@ztanner ztanner closed this as not planned Won't fix, can't repro, duplicate, stale Jan 8, 2025
@mgmolisani
Copy link

Thanks for the response. This probably needs some sort of documentation then if the page is never going to be hydrated and won't run effects. Currently, it just says we can slap "use client" on there which makes it seem like this should work in every context. While there are many developers who will understand this limitation, it's not uncommon in our space for someone unfamiliar to pick up a loading spinner library using JS/effects just to find out it mysteriously will not work. Saying to just use CSS isn't often an answer and doesn't cover many animation/sizing/etc use cases.

Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. locked
Projects
None yet
Development

No branches or pull requests

7 participants