vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.51k stars 26.81k forks source link

Ref gets lost during Suspense execution #67542

Open nirus opened 3 months ago

nirus commented 3 months ago

Link to the code that reproduces this issue

https://github.com/nirus/nextjs-ref-ssue

To Reproduce

  1. Clone the application
  2. Run the application is SSR debug mode in Vs-code (launch.json)
  3. Observe the console.log in terminal window

Current vs. Expected behavior

useModuleImportSuspense throws the promise and cause the SSR to suspend and refs loose the value on subsequent rendering when resolved.

Current Behaviour: HELLO THERE ---> false HELLO THERE ---> false ..(loop)

Expected Behaviour HELLO THERE ---> false HELLO THERE ---> true

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.6.0: Wed Aug 10 14:28:25 PDT 2022; root:xnu-8020.141.5~2/RELEASE_ARM64_T8110
  Available memory (MB): 16384
  Available CPU cores: 8
Binaries:
  Node: 20.11.0
  npm: 10.2.4
  Yarn: 1.22.19
  pnpm: 8.11.0
Relevant Packages:
  next: 14.2.4 // Latest available version is detected (14.2.4).
  eslint-config-next: 14.2.4
  react: 18.3.1
  react-dom: 18.3.1
  typescript: 5.5.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Lazy Loading, Performance

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local)

Additional context

Similar bug on preact - https://github.com/preactjs/preact-ssr-prepass/issues/23

nirus commented 3 months ago

Temporary solution:

By modifying https://github.com/nirus/nextjs-ref-ssue/blob/main/src/pages/index.tsx with below code.

Explanation : I don't rely on useRef but use globals promiseResult & store to store resolved result and ongoing promise within module it works and gets executed on client.

Code:

import { Inter } from "next/font/google";
import { Suspense } from "react";

const inter = Inter({ subsets: ["latin"] });

const promise: () => Promise<{
  Test: () => string;
}> = () => new Promise((resolve) => resolve({ Test: () => "Test" }));

let promiseResult: null | {
  Test: () => string;
} = null;

let store: null | Promise<{
  Test: () => string;
}> = null;

const TestComponent = () => {
  if (!store) {
    store = promise().then((result) => {
      promiseResult = result;
    }) as Promise<{
      Test: () => string;
    }>;
  }

  if (!promiseResult) {
    throw store;
  }

  return <div>Test {promiseResult?.Test()} </div>;
};

export default function Home() {
  return (
    <>
      <Suspense fallback={<div>Loading...</div>}>
        <TestComponent />
      </Suspense>
    </>
  );
}
nirus commented 2 months ago

Any update on the issue?

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!