Using Server Actions with onClick={..}
Answered
Kangal Shepherd Dog posted this in #help-forum
Kangal Shepherd DogOP
Hello, it works but I wanted to ask
Is calling server action with button onclick in client component good practice or it should be avoided?
Example:
<button onClick={() => myServerAction()} >
Is calling server action with button onclick in client component good practice or it should be avoided?
Example:
<button onClick={() => myServerAction()} >
Answered by Greater Shearwater
Yeah while you can do it, I wouldn't call it a good practice. You lose the progressive enhancement this way. Meaning, for the
If you instead called the server action via the
Also, since server actions are used for POST requests (create, edit, delete), it also semantically makes sense to me to wrap any of those operations in a form. Because user is submitting some data. So I personally won't have just a button calling a server action alone.
onClick to work the JS must be loaded on the client and the page should be hydrated. Until then this won't work.If you instead called the server action via the
action prop on the form, or the formAction prop on a button when the button is inside a form, the submission will work even before the JS is loaded and hydration happens.Also, since server actions are used for POST requests (create, edit, delete), it also semantically makes sense to me to wrap any of those operations in a form. Because user is submitting some data. So I personally won't have just a button calling a server action alone.
8 Replies
Greater Shearwater
Yeah while you can do it, I wouldn't call it a good practice. You lose the progressive enhancement this way. Meaning, for the
If you instead called the server action via the
Also, since server actions are used for POST requests (create, edit, delete), it also semantically makes sense to me to wrap any of those operations in a form. Because user is submitting some data. So I personally won't have just a button calling a server action alone.
onClick to work the JS must be loaded on the client and the page should be hydrated. Until then this won't work.If you instead called the server action via the
action prop on the form, or the formAction prop on a button when the button is inside a form, the submission will work even before the JS is loaded and hydration happens.Also, since server actions are used for POST requests (create, edit, delete), it also semantically makes sense to me to wrap any of those operations in a form. Because user is submitting some data. So I personally won't have just a button calling a server action alone.
Answer
Kangal Shepherd DogOP
Thank you so much! If you dont mind, I would have one more question.
I was designing cookie consent, and what I tried to do is call server action in root layout on load.
export default async RootLayout(){
const cookieStore = await cookies()
if(!cookieStore.has("cookie_consent")){
myServerAction()
}
...
return (
...
...
...
)
I got error that cookies can be added or modified only through server actions or middleware, even tho I called the same serverAction which worked in different component as onClick()
I, of course, used middleware then, but I am wondering - why modifying cookie through server action in RootLayout() returned error, as if I tried to modify cookies directly, without calling server action
I was designing cookie consent, and what I tried to do is call server action in root layout on load.
export default async RootLayout(){
const cookieStore = await cookies()
if(!cookieStore.has("cookie_consent")){
myServerAction()
}
...
return (
...
...
...
)
I got error that cookies can be added or modified only through server actions or middleware, even tho I called the same serverAction which worked in different component as onClick()
I, of course, used middleware then, but I am wondering - why modifying cookie through server action in RootLayout() returned error, as if I tried to modify cookies directly, without calling server action
Sorry if this is a stupid question, but I hope it can provide me some insight into inner works
When you call a Sever Action from a Server Components it’s just like you’re calling another regular function that happens to run on the server.
You can’t modify cookies in Server Components. And even if you’re calling the same “server action” this isn’t behaving like one of you call it from the server. So for the compiler you’re directly modifying cookies in server components.
It works when you call the Server Action from the client component, because now it behaves as it’s supposed to.
You can’t modify cookies in Server Components. And even if you’re calling the same “server action” this isn’t behaving like one of you call it from the server. So for the compiler you’re directly modifying cookies in server components.
It works when you call the Server Action from the client component, because now it behaves as it’s supposed to.
American black bear
For your use case using a server actions is completely fine, and probably the best practice.
American black bear
However the way you implemented the cookie consent is bad. As @LuisLl has said calling a server action in a server component is bad and should be avoided in favor of a normal server function. Do something like this instead:
export default async function RootLayout({children}) {
const cookieStore = await cookies();
const userIsConsent = cookieStore.has("cookie_consent");
return (
<div>
{userIsConsent ? <Analytics /> : <AskForCookieConsent />}
{children}
</div>
)
}American black bear
"use client"
export function AskForCookieConsent() {
const [answered, setAnswered] = useState(false)
function handleClick(isConsent) {
if (isConsent) {
// server action adding the cookie
createConsentCookieAction()
}
setAnswered(true)
}
if (answered) {
return null
}
return (
<div>
<div>Do you agree to use cookies?</div>
<button onClick={handleClick(false)}>No</button>
<button onClick={handleClick(true)}>Yes</button>
</div>
)
}@American black bear tsx
"use client"
export function AskForCookieConsent() {
const [answered, setAnswered] = useState(false)
function handleClick(isConsent) {
if (isConsent) {
// server action adding the cookie
createConsentCookieAction()
}
setAnswered(true)
}
if (answered) {
return null
}
return (
<div>
<div>Do you agree to use cookies?</div>
<button onClick={handleClick(false)}>No</button>
<button onClick={handleClick(true)}>Yes</button>
</div>
)
}
Kangal Shepherd DogOP
Oh, thank you for your answers!
so:
1) Calling server action directly from server components acts as if it was a normal function
2) If I want to use server action in server component, it has to be through form
3) I can call server action directly from client component
P.S.:
As I mentioned, I managed to get it working using middleware instead, but your way is exactly what I wanted to do originally.
What I did is, that the consent cookie is created immediately using middleware
Conditional in rootlayout
And inside the CookieConsentBar, I Have button with server actions setting the value of cookie as server actions
so:
1) Calling server action directly from server components acts as if it was a normal function
2) If I want to use server action in server component, it has to be through form
3) I can call server action directly from client component
P.S.:
As I mentioned, I managed to get it working using middleware instead, but your way is exactly what I wanted to do originally.
What I did is, that the consent cookie is created immediately using middleware
export default function middleware(req) {
let res;
...
if(!req.cookies.has("cookie_consent")) {
res.cookies.set("cookie_consent", "0", {path: "/"})
}
return res
}Conditional in rootlayout
export default async function RootLayout({ children, params }) {
const { locale } = await params;
const rootJSONLDs = await getRootJSONLDs();
if (!hasLocale(routing.locales, locale)) {
notFound();
}
const cookieStore = await cookies();
return (
...
{cookieStore.get("cookie_consent").value === "0" && (
<CookieConsentBar />
)}
{cookieStore.get("cookie_consent").value === "2" && ( <Analytics/>)
...
);
}And inside the CookieConsentBar, I Have button with server actions setting the value of cookie as server actions