Next.js Discord

Discord Forum

Upgrading to Next 14 broke returning data through server actions?

Answered
Silky ant posted this in #help-forum
Open in Discord
Silky antOP
// actions.ts
"use server";

export async function getExercises() {
  const { userId } = auth();
  if (!userId) return [];

  const data = await db
    .select()
    .from(exercise)
    .where(eq(exercise.userId, userId))
    .orderBy(desc(exercise.id))
    .all();

  return data;
}

// inside client component
  const { data } = useQuery({
    queryKey: ["exercises"],
    queryFn: getExercises,
    initialData: exercises,
  });

// inside server component
  const exercises = await getExercises();


this used to work just fine in next 13, now i get errors everywhere

am I supposed to go back to api routes again? what's the best practice with them? any way to not write duplicated code?
Answered by Ray
if it works, try changing the queryFn to
async () => {
  return getExercises()
}
View full answer

121 Replies

Silky antOP
is that the first error?
Silky antOP
thats the whole console
what if you comment
  const { userId } = auth();
  if (!userId) return [];
cant seem to see where the problem is. maybe identify which line causes the error?
Silky antOP
i have tried that, didn't work
the function doenst erorr and data is correct
Silky antOP
this also returns the correct data
so is it really server action's fault?
seems like its something else
Silky antOP
i think so
Uncaught (in promise) Error: Only plain objects, and a few built-ins, can be passed to Server Actions. Classes or null prototypes are not supported.
what did you pass to server actions?
which server action is that?
Silky antOP
the getExercises() function
shown above
what is userId
no wait
what is exercise. where does it come from
Silky antOP
i tried api route on client and copy pasting the function body in the server component and it seemed to work, it worked, page wasnt getting blocked but loading seemed to take very long compared to server action in 13
@aardani what is `userId`
Silky antOP
a string
@aardani what is `exercise`. where does it come from
Silky antOP
comes from drizzle, its a table
comment out this part
Silky antOP
i cant return data then
ok ive done this and it still errors
export async function getExercises() {
  const { userId } = auth();
  if (!userId) return [];

  // const data = await db
  //   .select()
  //   .from(exercise)
  //   .where(eq(exercise.userId, userId))
  //   .orderBy(desc(exercise.id))
  //   .all();

  // console.log(data);
  // console.log(userId);
  // console.log(exercise);

  // return data;
  return [];
}
comment out auth()?
Silky antOP
still errors even if all it does is return [
]
what if you dont use getExercises()?
will the error persists?
Silky antOP
nope
try moving getExercises() into a new "use server" file
Silky antOP
still doesnt work
changed to alias import, changed file location, no succes still
try reproducing it in minimal reproduction repository if you need to
Silky antOP
yeah its def an internal issue
with server actions on client
not sure if its intentional of them
brand new project

// page.tsx
"use client";

import { getExercises } from "./actions";
import { useQuery } from "@tanstack/react-query";

export default function Home() {
  const { data } = useQuery({
    queryKey: ["exercises"],
    queryFn: getExercises,
    initialData: [],
  });

  return <div>test</div>;
}

// layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { Providers } from "./providers";

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

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

// providers.tsx
"use client";

import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient());

  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
}

// actions.ts
"use server";

export async function getExercises() {
  return [];
}
Miniature Bull Terrier
You can't directly import server actions in 14 (bug or by design?)
You should make your page.tsx SSR (remove 'use client'), then create a sibling component that's use client
import your server actions in your page.tsx and pass it as a prop to the child client component
Silky antOP
the page.tsx is actually server side on my actual app, but thank you I'll try passing it down as props
yeah just pass the action to the client component, the client component can still be using the imported one
until they fix it
Silky antOP
nvm someone replied that it got fixed in 14.0.2-canary.11

https://github.com/vercel/next.js/issues/57622#issuecomment-1793820186
thank you very much guys, ill either upgrade or pass it as props
Miniature Bull Terrier
that comment is not 100% correct, it's still broken (see the response)
@Miniature Bull Terrier that comment is not 100% correct, it's still broken (see the response)
Silky antOP
nvm yes you're right. but passing it as props still didnt work either
Silky antOP
tested in both canary and 14.0.2, cant just import and cant pass it as props either, how are they even called stable? they're more broken than ever
turbopack also breaks the app
// page.tsx
import Exercises from "./Exercises";
import ExerciseForm from "@/components/ExerciseForm";
import { getExercises } from "../actions";

export default async function Page() {
  const exercises = await getExercises();

  return (
    <>
      <h1 className="text-center text-5xl font-bold">Exercises</h1>
      <div className="grid place-items-center gap-5 md:flex md:flex-row-reverse md:items-start md:justify-between">
        <ExerciseForm />
        <Exercises exercises={exercises} getExercises={getExercises} />
      </div>
    </>
  );
}

// Exercises.tsx
"use client";

import { useQuery } from "@tanstack/react-query";
import { Exercise } from "@/lib/types";
import ExerciseCard from "@/components/ExerciseCard";
// import { getExercises } from "../actions";

export default function Exercises({
  exercises,
  getExercises,
}: {
  exercises: Exercise[];
  getExercises?: () => Promise<Exercise[]>;
}) {
  const { data } = useQuery({
    queryKey: ["exercises"],
    queryFn: getExercises,
    initialData: exercises,
  });

  return (
    <div className="grid grid-cols-1 items-start gap-10 lg:grid-cols-2 xl:grid-cols-3">
      {!data.length && <p>you have no exercises</p>}
      {data.map((exercise) => (
        <ExerciseCard key={exercise.id} exercise={exercise} />
      ))}
    </div>
  );
}
      
please tell me im doing something wrong and next isnt this unstable
is that auth function comming from next-auth? maybe try comment it and hard coding the user id?
look like it breaks because of using external package
Silky antOP
nah
its clerk and it works fine, ive console logged it, and even if all the server actions does is return an empty array it still doesnt work
theyre just broken
try calling getExercises directly in the client component instead of pass it to useQuery
see if you can get the data
I think its the problem with react-query, because the queryFn pass arg to the server action
Silky antOP
it worked just fine in 13.3/13.4
maybe something changed in 14
not sure
Silky antOP
some alternative to tanstack query?
does it work?
if it works, try changing the queryFn to
async () => {
  return getExercises()
}
Answer
Silky antOP
i cant test if it works, cant call an async function inside a client component
try change the queryFn
Silky antOP
one second
    queryFn: async () => {
      return await getExercises();
    },
omg this works
Silky antOP
this a shorter version
    queryFn: async () => await getExercises(),
smh thank you so much man
i think you can omit the await too
then you can try importing the server action directly instead of passing it
Silky antOP
yeah it works
great
Silky antOP
i get a bunch of errors in the console tho
theres a million of these
lol
check the network tab in console
is it keep making request?
Silky antOP
yes
355 requests lol
nvm it keeps going
well, properly server action does not work with react-query
the doc say its for server mutation
Silky antOP
ill see how i can optimise, thank you for the fix
@Ray well, properly server action does not work with react-query
Many have point out its integration with react query in next 13, not aure why it has stopped working in 14
@Silky ant i just tried and it works fine with just one request made
Silky antOP
what react query version are you on?
i didnt upgrade to v5 because it changes some stuff and id have to rewrite in many places
but will probably do it anyway at some time in the future
"@tanstack/react-query": "^5.8.1",
what version you using
just tried v4 and it also works
"@tanstack/react-query": "^4.36.1",
Phew so it wasnt server action
@Silky ant ts queryFn: async () => { return await getExercises(); },
this fixed the error he got early but his app make bunch of request when page loaded
@Ray the doc say its for server mutation
For the record data fetching is fine too but it needs to be runtime data fetching not something you are able to do with initial data fetching at server components
@Ray this fixed the error he got early but his app make bunch of request when page loaded
Isnt that the problem with default usequery settings?
well but I just tried it only make one request
I know
@Silky ant Click to see attachment
take a look what are these request calling to?
Silky antOP
it works fine but idk why are there 80mb worth of requests, not sure if it was the same on 13.3
@Ray take a look what are these request calling to?
Silky antOP
yeah I'll check tomorrow
Miniature Bull Terrier