Next.js Discord

Discord Forum

Proplems with server actions

Answered
Transvaal lion posted this in #help-forum
Open in Discord
Transvaal lionOP
I am pretty new with next Js. But. I have an app where i whant to give to every client a diferent id. To do that i have a server action that gets the client id and if it dosen't exist it's going to create it. I am using cookies-next.
export async function getClientId() {
    const id = await getCookie("client-id");
    if (!id) {
        const newId = crypto.randomUUID();
        setCookie("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
        return newId;
    }
    return id;
}

The problem is that something is wrong because the cookie dosen't get saved on the client if i add
<Button variant="link" onClick={getClientId} className="mt-4">Obține Client ID</Button>

to a file that is marked with "use server" it does not work. It generates a new cookie on every click.
Answered by alfonsüs ardani
yes so you can't setCookies in server components
View full answer

77 Replies

Transvaal lionOP
They are from the library cookies-next
@alfonsüs ardani i think you need to `await setCookie()`
Transvaal lionOP
Nah tried it
try without using cookie-next
Transvaal lionOP
The thing is if i specify to use cookies-next/server next js complains about client using server functions
@alfonsüs ardani try without using cookie-next
Transvaal lionOP
I think cookies-next is a wrapper for next headers and document.cookies
And yes i tried to use next headers
I got the error that i use server functions on the client
you're overcomplicating the issue.

it works with just (await cookies()).set(...)
its not about server actions but about something else
probably about your usage of cookies-next/server
Transvaal lionOP
I tested (await cookies()).set() and .get and i think it didn't work i am able to test this in 20 min
The thing is that in the same file i use fs so it has to be the server. And when i call the server the server action gets called
When i call the function
allright, please send complete minimal reproducible code because at this point im confused whether or not you've set up server action correctly or not
i.e where you put "use server", and where you put "use client"
Transvaal lionOP
Ok. I can provide code in 20 min
"in the same file" like which file? The file of <Button> or the file of getClientId?
Transvaal lionOP
In the same file the getClientId is declared i have another function that uses fs
Transvaal lionOP
so i cropped the file from all the functions i have inside it but:
"use server";

import fs, { access, constants } from "node:fs/promises";
import { getCookie, setCookie } from "cookies-next";
import { v4 as uuidv4 } from "uuid";
import path from "node:path";

export async function getClientId() {
    const id = await getCookie("client-id");
    if (!id) {
        console.warn("Client ID este hardcodat.")
        const newId = "f8f16d33-62a1-4852-8386-7bb5fbbb55c2"; //crypto.randomUUID();
        await setCookie("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
        return newId;
    }
    return id;
}
and in a page.tsx i have
import UploadForm from "../../components/UploadForm";
import { getClientId, listUploadedFiles } from "./server";
import { Button } from "@/components/ui/button";

export default async function Home() {
    const files = await listUploadedFiles();

    return (
        <main>
            <div className="flex flex-wrap justify-center m-8">
                <UploadForm />
                <Button variant="link" onClick={getClientId} className="mt-4">Obține Client ID</Button>
            </div>
        </main>
    );
}
thanks for sending me minimal code.
can we also have the error log and whats inside of <Button> ?
Transvaal lionOP
no errors. and in the DOM ?
in the file it has text
no errors so it works?
Transvaal lionOP
no sorry i added that hardcoded id because it didn't work and i needed to test other parts of the app
@alfonsüs ardani no errors so it works?
Transvaal lionOP
no errors and it dosen't work
POST /uploads 200 in 54ms (compile: 25ms, render: 29ms)
Client ID este hardcodat.
GET /upload 200 in 727ms (compile: 413ms, render: 314ms)
lets just try making the code more minimal and troubleshoot this
try using <form action={getClientId}> instead of <Button>
Transvaal lionOP
ok. should i wrap the button with that from and make the button type submit?
no just do simple <form action={getClientId}><button>Submit</button></form>
Transvaal lionOP
<form className="ml-4" action={() => { getClientId() }}>
<button>Obține Client ID</button>
</form>. the function returns something and ts isn't happy about it.
have you tried the exact code i wrote?
Transvaal lionOP
yea and it still makes a new id on each call
        <main>
            <div className="flex flex-wrap justify-center m-8">
                <UploadForm />
                <form action={getClientId}>
                    <button>Obține Client ID</button>
                </form>
            </div>
        </main>
have you tried without using cookies-next ?
Transvaal lionOP
i
i'l try
thank you
Transvaal lionOP
⨯ Error: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#options
at getClientId (app\upload\server.tsx:93:21)
at async listUploadedFiles (app\upload\server.tsx:78:22)
at async Home (app\upload\page.tsx:7:19)
91 | const newId = crypto.randomUUID(); //"f8f16d33-62a1-4852-8386-7bb5fbbb55c2"; //
92 | console.warn("Client ID este este:", newId)
93 | cookieStore.set("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
| ^
94 | return newId;
95 | }
96 | return id.value; {
digest: '1433263048'
}
GET /upload 200 in 517ms (compile: 7ms, render: 511ms)
that shows that you've used getClientId somewhere else
not in Button :(
in here const files = await listUploadedFiles();
Transvaal lionOP
that is in the same file. on top the the getclientId declaration
export async function listUploadedFiles() {
    const clientId = await getClientId();
    const dirPath = `${process.env.FILESSTORAGEPATH}/${clientId}`;
    if (!await fileExists(dirPath)) {
        return [];
    }
    const files = await fs.readdir(dirPath);
    return files;
}

export async function getClientId() {
    const cookieStore = await cookies();
    const id = cookieStore.get("client-id");
    if (!id) {
        const newId = crypto.randomUUID(); //"f8f16d33-62a1-4852-8386-7bb5fbbb55c2"; //
        console.warn("Client ID este este:", newId)
        cookieStore.set("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
        return newId;
    }
    return id.value;
}
yes so you can't setCookies in server components
Answer
your getClientId is in listUploadedFiles
your listUploadedFiles is in Home()
Home() is a server component
can't set cookie in server components
Transvaal lionOP
so what do i need to do ?
dont set cookie in getClientId
Transvaal lionOP
were should i set it?
use ClientComponent that runs on useEffect, and call ensureClientIdExistAction() that uses "use server" and make sure "client-id" cookie exists. if not then cookieStore.set or setCookies()
put ClientComponent at root layout so that it doesn't re-render on navigation
Transvaal lionOP
so. I should make a new function in the use server file that makes the cookie and to call it with an use effect in the layout page like this:
the layout page
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  useEffect(() => {
    ensureClientIdExistAction();
  }, []);

  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

and
export async function ensureClientIdExistAction() {
    const newId = uuidv4();
    const cookieStore = await cookies();
    cookieStore.set("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
}
do i need to make the layout page client
Transvaal lionOP
so
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <EnsureCookie />
        {children}
      </body>
    </html>
  );
}

with
"use client"

import { ensureClientIdExistAction } from "@/app/upload/server";
import { useEffect } from "react";

function EnsureCookie() {
  useEffect(() => {
    ensureClientIdExistAction();
  }, []);

  return (
    <div />
  )
}

export default EnsureCookie
yeah
something like that
just return <></>
its fine
Transvaal lionOP
export async function ensureClientIdExistAction() {
    const cookieStore = await cookies();
    if (cookieStore.get("client-id")) {
        return;
    }
    const newId = uuidv4();
    cookieStore.set("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
}
awesome glad it works!
now you can proceed in using cookies-next for your convenience
Transvaal lionOP
tho. i don't know if i can do this
export async function getClientId() {
    const cookieStore = await cookies();
    const id = cookieStore.get("client-id");
    if (!id) {
        throw new Error("Client ID nu există în cookie-uri.");
    }
    return id.value;
}
as it doesn't seem to get called before the other components
and i replaced it with
export async function getClientId() {
    const cookieStore = await cookies();
    const id = cookieStore.get("client-id");
    if (!id) {
        //throw new Error("Client ID nu există în cookie-uri.");
        return "";
    }
    return id.value;
}
as this fixed it but i wished it didn't it dosen't seem to be a clean sollution
you can try doing something like

export async function getClientId() {
    const cookieStore = await cookies();
    const id = cookieStore.get("client-id");
    if (!id) {
        const newId = crypto.randomUUID();
        console.warn("Client ID este este:", newId)
        try {
          cookieStore.set("client-id", newId, { maxAge: 60 * 60 * 24 * 365 * 20 });
        } catch { }
        return newId;
    }
    return id.value;
}


if you want cleaner solution
Transvaal lionOP
thanks