Calling server functions from useEffects leads to undefined responses or throwing server functions
Unanswered
West African Lion posted this in #help-forum
West African LionOP
Hey everyone,
We’re heavily using Server Actions in our self-hosted Next.js 15 app, sometimes directly invoked from client-side code—specifically inside useEffect hooks or within TanStack Query’s useQuery and useMutation.
We’ve noticed two recurring issues:
• Sometimes the server action returns undefined, and we don’t see any logs on the server, indicating the action might not have run at all.
• Mostly, we encounter server functions throwing the error: An unexpected response was received from the server.
The only hint we have is that this probably happens only after opening our page, either from SEO for example, or mainly when coming back from our third party payment provider.
Does anyone recognize this behavior or have insights into why this happens?
Are there known limitations when using Server Actions within useEffect or TanStack Query hooks?
Any suggestions on how we can further investigate or debug this issue would be greatly appreciated!
Right now we are about to move everything to tRPC and route handlers.
Thanks in advance!
We’re heavily using Server Actions in our self-hosted Next.js 15 app, sometimes directly invoked from client-side code—specifically inside useEffect hooks or within TanStack Query’s useQuery and useMutation.
We’ve noticed two recurring issues:
• Sometimes the server action returns undefined, and we don’t see any logs on the server, indicating the action might not have run at all.
• Mostly, we encounter server functions throwing the error: An unexpected response was received from the server.
The only hint we have is that this probably happens only after opening our page, either from SEO for example, or mainly when coming back from our third party payment provider.
Does anyone recognize this behavior or have insights into why this happens?
Are there known limitations when using Server Actions within useEffect or TanStack Query hooks?
Any suggestions on how we can further investigate or debug this issue would be greatly appreciated!
Right now we are about to move everything to tRPC and route handlers.
Thanks in advance!
6 Replies
Southern rough shrimp
You need to show code samples for anybody to help you.
You shouldn’t be using Server Actions for data fetching. They’re meant to be used for mutations only. The issue is that they run sequentially and not in parallel. Also, server actions make a POST request under the hood, which makes no sense to GET data.
Route Handlers is the way to go for fetch in data from client components, that, or pass data from server components down to their children.
tRPC is still a very nice solution for these patters in Next.js.
Route Handlers is the way to go for fetch in data from client components, that, or pass data from server components down to their children.
tRPC is still a very nice solution for these patters in Next.js.
Especially, do not call server actions to get data inside of useEffects. If state changes too often and triggers the Server Action to run again, you’ll have problems like these, where server actions seem to never finish, since only [one Server Action can be processed at at time](https://react.dev/reference/rsc/use-server#caveats)
West African LionOP
Thanks a lot. Here are two simplified examples, but I guess that they don't help any more.
// client-side
const { data } = useQuery({
queryKey: ['getFeatureXVariant'],
queryFn: () => getFeatureXVariant(),
});
// intermediate function
const getFeatureXVariant = async () => {
const { isEnabled, variant } = await getFeatureVariant(FEATURE_X);
return { isEnabled, variant };
};
// server-side
'use server';
export const getFeatureVariant = async (feature: string) => {
const flagsClient = await getFlagsClient();
return flagsClient.getVariant(feature);
};
// client-side
useEffect(() => {
if (data) {
void handleOrderStatusResponse(data, replace);
}
}, [data, replace]);
// intermediate function
async function handleOrderStatusResponse(orderSubmitResult, redirector) {
if (orderStatus.status === OrderStatus.COMPLETED) {
await clearBasket();
redirector(`${AppRoutes.PurchaseSuccess}?orderId=${orderStatus.orderId}`);
}
}
// server-side
'use server';
export const clearBasket = async () => {
const emptyBasket = getEmptyBasket();
await updateBasket(emptyBasket);
return emptyBasket;
};
It absolutely makes sense now what you @LuisLl said about sequential calling, since we handle these calls as fetch calls in Promise.all quite some times. This is not properly reflected in the examples due to the complexity.
Can someone still explain why I shouldn't fetch data in server functions, since I can for sure return data from them even when doing mutations. What is the difference then from mutation to fetching if the server does some logic and returns data in the end?
And is is then also discouraged to use Tanstack Mutations?
Can someone still explain why I shouldn't fetch data in server functions, since I can for sure return data from them even when doing mutations. What is the difference then from mutation to fetching if the server does some logic and returns data in the end?
And is is then also discouraged to use Tanstack Mutations?
@West African Lion It absolutely makes sense now what you <@744377057139753020> said about sequential calling, since we handle these calls as fetch calls in Promise.all quite some times. This is not properly reflected in the examples due to the complexity.
Can someone still explain why I shouldn't fetch data in server functions, since I can for sure return data from them even when doing mutations. What is the difference then from mutation to fetching if the server does some logic and returns data in the end?
And is is then also discouraged to use Tanstack Mutations?
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.
This is written in the React Docs (linked above) . Plus, Server Actions make a POST request under the hood, which is not semantically correct for data fetching only.
When you do a mutation it’s possible (and pretty common) to return data back to the user, but that’s data is understood as the result of the mutation, not of a query.