Proplems with server actions
Answered
Transvaal lion posted this in #help-forum
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.
The problem is that something is wrong because the cookie dosen't get saved on the client if i add
to a file that is marked with "use server" it does not work. It generates a new cookie on every click.
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.
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
it works with just
(await cookies()).set(...)its not about server actions but about something else
probably about your usage of
cookies-next/serverTransvaal 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)
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.
<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)
94 | return newId;
95 | }
96 | return id.value; {
digest: '1433263048'
}
GET /upload 200 in 517ms (compile: 7ms, render: 511ms)
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
and
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 clientcreate Client Component, put "use client" on top, put Client Component on RootLayout.
and dont make the Action like that. Use the same code that you write like
if(!id){ ... }Transvaal lionOP
so
with
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@alfonsüs ardani and dont make the Action like that. Use the same code that you write like `if(!id){ ... }`
Transvaal lionOP
🤦♂️ did not see that
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
if you want cleaner solution
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