In React 19, I'm trying to create a Server Function that works with a React client component (not a React Server Component) to perform an async fetch. If I put the async fetch directly in the client component, it fetches the data with no error, but when I move the async fetch into Server Function, I get an error as follows:
Can't perform a React state update on a component that hasn't mounted yet.
This indicates that you have a side-effect in your render function that
asynchronously later calls tries to update the component.
Move this work to useEffect instead.
It feels like it should be unnecessary to add either useEffect
or useState
to React 19 code that includes the new use hook and Suspense.
I have both ways of doing this in this GitHub repo: https://github.com/pkellner/react19-use-hook-server-function-issue
The problem code, that is the code that gets the error is this:
"use client";
import { use, Suspense } from "react";
import { fetchUsers } from './actions';
const userPromise = fetchUsers();
const Users = () => {
const users = use(userPromise);
return (
<div>
<h1>Client Side Only (use hook and suspense)</h1>
<ul>
{users.map((user) => (
<div key={user.id}>
<h2>{user.name}</h2>
</div>
))}
</ul>
</div>
);
};
function App() {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Users />
</Suspense>
);
}
export default App;
and it's server action is:
'use server';
export async function fetchUsers() {
await new Promise((resolve) => setTimeout(resolve, 1000));
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json();
}
The code that does not use a Server Function and works without error is this:
"use client";
import { use, Suspense } from "react";
const fetchUsers = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const res = await fetch("https://jsonplaceholder.typicode.com/users");
return res.json();
};
const userPromise = fetchUsers();
const Users = () => {
const users = use(userPromise);
return (
<div>
<h1>Client Side Only (use hook and suspense)</h1>
<ul>
{users.map((user) => (
<div key={user.id}>
<h2>{user.name}</h2>
</div>
))}
</ul>
</div>
);
};
function App() {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Users />
</Suspense>
);
}
export default App;
I'm not sure where you got the error message from, but I tried your project locally and encountered this error message:
Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.
This is expected behavior because the server function is not supposed to be called during rendering. If you’re wondering why, it’s simply by design, and this error serves as proof. The React documentation also states this
Server Functions are designed for mutations that update server-side state; they are not recommended for data fetching.
You can import and call server functions in event listeners (e.g., form actions, button clicks), but not directly during rendering.
The correct way to use server actions to fetch data in client components is to call them in the server component and pass the promise as a prop to the client component. This allows Next.js to fetch data on the server and stream the result to the client.
// page.tsx
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
export default function Page() {
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
// posts.tsx
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const posts = use(posts)
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}