Skip to content
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

Calling a server action during render tries to update Router component, causing an error #75374

Open
jonathanhefner opened this issue Jan 27, 2025 · 5 comments
Labels
Runtime Related to Node.js or Edge Runtime with Next.js.

Comments

@jonathanhefner
Copy link
Contributor

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/crazy-field-gnyj7q

To Reproduce

  1. Visit the reproduction app.

  2. In the app preview, see the Next.js error pop-up:

    Console Error

    Cannot update a component (Router) while rendering a different component (Demo). To locate the bad setState() call inside Demo, follow the stack trace as described in https://react.dev/link/setstate-in-render

    Note that there is no explicit setState() call inside the component.

Current vs. Expected behavior

Currently, calling a server action during a render causes a React error (see above).

The expected behavior is that the server action returns a Promise (without causing a React error), and that the Promise, if properly cached, can be consumed with use (without causing a React error).

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
  Available memory (MB): 4102
  Available CPU cores: 2
Binaries:
  Node: 20.9.0
  npm: 9.8.1
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 15.2.0-canary.27 // Latest available version is detected (15.2.0-canary.27).
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

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

Runtime

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

next dev (local), next start (local)

Additional context

No response

@github-actions github-actions bot added the Runtime Related to Node.js or Edge Runtime with Next.js. label Jan 27, 2025
@icyJoseph
Copy link
Contributor

The new Router is fully integrated with React. In practical terms, that means that it uses React state, namely, a useReducer lives at the heart of it.

And just like you shouldn't call dispatch, nor setState from useState, unless you really think you know what you are doing, you can't run router updates, within the render body of a function component. You have to use callbacks, effects, interactivity, etc.

So, what I think is happening here is that, the act of calling an action, updates the actionQueue, which leads to this issue.

Where is it stated that one can call server actions during render? That documentation, probably, needs fixing.

@jonathanhefner
Copy link
Contributor Author

@icyJoseph Thank you for the reply!

Where is it stated that one can call server actions during render?

Where is it stated that you can't?

I realize there is still ongoing work around use, such as adding an official mechanism for promise caching, but prohibiting the use of server actions would be an unexpected and unreasonable limitation.

Also, I observed that "delaying" the server action call with Promise.resolve() prevents the error:

--- a/app/client-components.tsx
+++ b/app/client-components.tsx
@@ -1,12 +1,17 @@
 'use client'

-import { use } from "react"
+import { use, useEffect, useState } from "react"
 import { getValue } from "./server-actions"

 let cachedPromise: Promise<string>

 export function Demo() {
-  cachedPromise ??= getValue()
+  // Avoid server action during SSR; avoid hydration error.
+  const [isFirstRender, setIsFirstRender] = useState(true)
+  useEffect(() => { setIsFirstRender(false) }, [])
+  if (isFirstRender) return null
+
+  cachedPromise ??= Promise.resolve().then(() => getValue())
   const value = use(cachedPromise)
   return <div>{value}</div>
 }

I wouldn't rely on that behavior in production code, but it hints at the possibility of deferring the internal setState() calls until after use throws the Promise.

@icyJoseph
Copy link
Contributor

Why wouldn't you use useActionState instead? The way I read the docs, they recommend calling the Server Function (they changed the name), within a transition, and a transition happens within a callback, etc... They also explicitly say that, server functions, should not be used to fetch data.

Server Functions are designed for mutations that update server-side state; they are not recommended for data fetching. Accordingly, frameworks implementing Server Functions typically process one action at a time and do not have a way to cache the return value.

Which is also fun, because, there's this other issue, #69265, which is also kind of counter intuitive.

@jonathanhefner
Copy link
Contributor Author

They also explicitly say that, server functions, should not be used to fetch data.

Yes, I know that they are not recommended for data fetching, but they are not prohibited, and some people are using them for that purpose. Server functions + React serialization format can provide a magical DX compared to fetch calls.

Long term, I think there will eventually be a variant of server functions designed for data fetching (i.e. parallelizable, cacheable). I've seen a proposal to special case server functions based on naming convention, for example using HTTP GET for server functions that begin with with get... (e.g. getData()).

@icyJoseph
Copy link
Contributor

Right... And they are calling them server functions now, but calling them as you render is not the same as coupling to them in effects and such. Those are my two cents anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Runtime Related to Node.js or Edge Runtime with Next.js.
Projects
None yet
Development

No branches or pull requests

2 participants