-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
[Bug]: Infinite re-render for async RSC stories #30317
Comments
** Disclaimer** This information might be inaccurate, due to it being generated automatically
|
Upon further testing, it seems to be happening on older versions (~8.4.7) as well for this specific minimal repro 🤔 For the best context on my original concern, the way I discovered it is by experimenting with the issue outlined here on the newest release of Storybook: amannn/next-intl#771 (reply in thread).
In this specific example, both Hence me opening this issue. There is probably a lot more underground currents here re: RSC/Suspense/etc that I am not seeing, so just trying to provide as much info as possible to help isolate and troubleshoot here. |
Hi @dmitrc, Thanks for posting the issue. I read through the |
Thanks @valentinpalkovic,
Also added some other components to play around with, including async no await and fake await (actually sync). Please let me know if I can clarify anything further or help in any way!
|
Likely a duplicate of #30033 |
See my comment there on #30033 Here is the diff between next.js v15.0.4-canary.26 and v15.0.4-canary.27 In my environment, I've isolated the regression behavior in storybook to this change. In other words when I revert my Next.JS version to v15.0.4-canary.26 it works. When I upgrade to v15.0.4-canary.27 it starts failing as the OP described. This Next.JS diff includes a React upgrade from 19.0.0-rc-380f5d67-20241113 to 19.0.0-rc-b01722d5-20241114. Here is the diff for those react versions which represents going from React RC 0 to React RC 1. Here is a post about React 19 RC 1 from the React team. It seems the big difference is that the React 19 enableSiblingPrerendering feature flag is switched to true. Not sure yet how or why this affects Storybook the way it does: causing infinite Suspense loops in RSC components. |
Thanks for looking into it, @boylec! It seems there are two distinct issues described in this thread:
|
Acknowledging that Storybook's handling of RSC is kind of funky to begin with, do we know if recent changes to Storybook contributed to these latest problems? Or are these entirely due to upgrading the version of React? |
I don't know the problem of losing the upstream context of the latter... |
EDIT: In hindsight, I would expect that the behavior of the Uncached component in @davidcarr-au 's example is by design. If you call the 'use' hook with a newly instantiated promise on each invocation, it should be expected to suspend infinitely. I think one should always pass the same instance to the 'use' hook if one expects it to eventually exit the suspense state. |
Digging deeper (specifically the infinite render issue in React 19-rc1), here’s my understanding: Pre-RC1: RC1: This is not exclusive to the new 'use' hook, same deal with rendering nested async components using the traditional await data-fetching pattern inside a Suspense boundary. Each render instantiates a new Promise, destabilizing the boundary and causing React to repeatedly suspend and re-render. In Nextjs, server actions make this behaviour stable by resolving data on the server, preventing the client from generating new Promises during rendering. However, Storybook doesn’t have this advantage, as it runs purely client-side, exposing issues with unstable async functions or Promises. So we can use promises and async components like we did pre-rc1 within storybook, but it requires a stabilisation mechanism, hence why the example i provided worked, i've resorted to running calls through a middleware to cache the promise and then remove it on first re-request when mocked for storybook. FYI the React docs also highlight this caveat: client-side Promises recreated on each render will cause unnecessary re-suspends: |
I am very confused, is there any workaround other than waiting for the Storybook fix? |
This sample was created from an idea, but it can render some Async Component. The problem is the case where the parent is also a Server Component and the Async Component is used as an inner component. const sleep = (msec: number) => new Promise(resolve => setTimeout(resolve, msec))
const InsideComponent = async () => {
await sleep(100)
return (
<p>inside</p>
)
}
export const OutsideComponent = () => {
return (
<div>
<InsideComponent /> {/*<- failed to render this component*/}
</div>
)
} Any more than this may require intervention in the internal renderer rather than the decorator https://gist.github.com/NEKOYASAN/f6e192194169bb9f22e8cb2ef2c84eca |
I think the correct fix for this is going to require internal renderer work. Seems like storybook needs to legitimately run a server-side render using react-dom/server against stories for frameworks that use React 19 RC 1+ such as Next.JS, then pipe that raw server HTML to react-dom/client hydrateRoot against the same story to get the final render. Or some variation like that. I'm not well-versed in the internals of storybook enough to make this contribution with the short time I have available. Thanks for the example @NEKOYASAN Unfortunately it doesn't get us by so we're stuck on next.js 15.0.4-canary.26 until RSCs get fixed permanently in storybook. |
You're probably right @boylec. We are booked up through 9.0, but I will discuss with the team to try to get it in as soon as we can manage it. If anybody wants to flex their RSC muscles and try to give this a shot, we are more than happy to advise. |
Thank you for your advice @NEKOYASAN Although it's a bit of a workaround, I avoided the issue by creating a package.json in a subdirectory and fixing the Next.js version that Storybook internally depends on to v15.0.4
// sb/package.json
{
"name": "subdirectory storybook",
"scripts": {
"storybook": "storybook dev --config-dir ../.storybook"
},
"devDependencies": {
"next": "15.0.4",
"storybook": "8.4.7"
}
} // package.json
{
"name": "project",
"scripts": {
"storybook": "yarn --cwd sb storybook"
},
"devDependencies": {
"next": "15.1.6",
"storybook": "8.4.7"
...
}
} Since the Next.js code used within Storybook depends on the packages installed in the runtime environment, this solution works. |
I knew it... At the moment there is no need to client references like in the RSC-based framework, and as @boylec wrote, it seems like a combination of |
@shilman I'm going to spend a few days on it and see how things go. Is Discord the best place to reach out if I need a tip or two? |
@boylec That's amazing. Yeah on SB discord there's a |
@shilman just fyi I'm blocked on running local sandbox against Next.JS prerelease due to a separate bug. @zhyd1997 has apparently addressed this issue with PR #30372 which is awaiting review. This would unblock me to move forward if it resolves the issue there. |
@boylec I just DMed @valentinpalkovic to see if he can merge. Bedtime here! I can look in the morning if it's still open |
Ah okay thanks. Actually I'll just pull in his changes locally until merged, so no biggie if it takes awhile. Thank you guys! |
|
I just downgraded Next.js to 15.0.4 and Storybook to 8.4.7 and it works, thx @kawamt . I'm gonna wait for a better solution, thankfully my project doesn't rely on last updates of the framework and the library. |
using react 18 with vite without nextjs was also causing this on local development. not on production though. so we also downgraded it to v8.4.7 |
@iamswain25 Do you have a reproduction you can share? |
we were manually triggering suspense by throwing an empty promise. I'll try my best. |
Describe the bug
There was always some funkiness around using
await
in RSC story renders in Storybook (eg see amannn/next-intl#771 (comment)), but in the version 8.5.0+ the behavior has changed for the worse.Now if I have a server component with
await
in it, some recent change on the Storybook side seems to be causing an infinite render. It's a bit tricky to point out what factor exactly causes this, as in my isolated repro even a singleawait
is enough to trigger it, whereas in my actual codebase it only seems to happen when there are 2+.Reproduction link
https://github.com/dmitrc/storybook-repro
Reproduction steps
Or setup from scratch:
.storybook/main.ts:
page.tsx:
ServerComponentWithOneAwait.stories.tsx:
System
Additional context
No response
The text was updated successfully, but these errors were encountered: