Tanmay Vaij
Tanmay Vaij

Reputation: 53

Passing callback from server component to client component in Next.js

I am stuck at a point where I am creating a custom button component, marking it as a client component, and then I am passing its onClick callback from a server-side component, and it's giving me this error:

 error Error: Event handlers cannot be passed to Client Component props.
  <button onClick={function} className=... children=...>
                  ^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
    at stringify (<anonymous>)
digest: "280571554"

This is my WelcomeComponent.tsx file:

import { faPizzaSlice } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Brand, Button } from "@/components/Common";
import Image from "next/image";

const WelcomeComponent = () => {
  return (
    <div className="border flex items-center justify-center flex-wrap space-x-10 py-10">
      <div className="font-semibold flex flex-col">
        <div className="mr-3 flex items-center text-3xl">
          <span className="mr-2">Welcome to</span>
          <Brand size="large" />
        </div>
        <h2 className="text-2xl">We Serve you with happiness.</h2>
        <Button
          text="Order Now"
          icon={<FontAwesomeIcon className="w-6" icon={faPizzaSlice} />}
          onClick={() => {
            console.log("c");
          }}
        />
      </div>
      <Image
        className="w-auto h-auto"
        height={0}
        width={300}
        src="/man.jpg"
        priority
        alt=""
      />
    </div>
  );
};

export default WelcomeComponent;

And this is my Button.tsx file:

interface Props {
  text: string;
  icon: React.JSX.Element;
  onClick: () => void;
}

const Button = ({ text, icon, onClick }: Props) => {

  return (
    <button
      onClick={onClick}
      className="shadow-lg my-5 flex items-center justify-center text-white bg-orange-400 hover:bg-orange-500 rounded-md p-3 w-40 space-x-2"
    >
      <span className="text-md">{text}</span>
      {icon}
    </button>
  );
};

export default Button;

As you can see I want to pass the onClick callback to the Button component which I am calling in the WelcomeComponent.tsx.

I'm using Next.js v13.4.

Upvotes: 5

Views: 5581

Answers (2)

Yilmaz
Yilmaz

Reputation: 49361

  • Server components run in the server environment, where Node.js execute the code. Client components run in the browser, which uses JavaScript (vanilla js) executed by the browser's JavaScript engine. Functions defined in the server environment cannot be directly executed in the client environment due to this difference.

  • Another issue is the serialization. Functions are complex objects and can't be directly serialized.

  • Security: server-side environment often has access to sensitive data, critical business logic, or operations that should not be exposed to the client. if you pass this sensitive coe to the client and If an attacker can manipulate the client-side JavaScript, they could potentially execute this function in ways that expose sensitive data. Or if the function is not properly sanitized, it might be susceptible to code injection attacks.

Upvotes: 0

Youssouf Oumar
Youssouf Oumar

Reputation: 45963

That's because Server Components gets compiled to HTML and sent to the browser with no additional JavaScript, that click handler in your case. And this makes it so you can only pass non-function values from a server component to a client one.

The component that calls Button should be a client one, with "use client" at the top, as you need to pass a function from there, and it's okay, as Lee Robinson from Vercel says:

Client Components aren't a de-opt, nor a cop-out. You can think of Server Components as an additive to the existing model. I understand the sentiment of wanting to use Server Components for lots of things (not bad!) but it's also totally okay to use Client Components for certain things.

"use client"; // Need to send a JavaScript function to the browser 👈🏽

import { faPizzaSlice } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Brand, Button } from "@/components/Common";
import Image from "next/image";

const WelcomeComponent = () => {
  return (
    <div className="border flex items-center justify-center flex-wrap space-x-10 py-10">
      <div className="font-semibold flex flex-col">
        <div className="mr-3 flex items-center text-3xl">
          <span className="mr-2">Welcome to</span>
          <Brand size="large" />
        </div>
        <h2 className="text-2xl">We Serve you with happiness.</h2>
        <Button
          text="Order Now"
          icon={<FontAwesomeIcon className="w-6" icon={faPizzaSlice} />}
          onClick={() => {
            console.log("c");
          }}
        />
      </div>
      <Image
        className="w-auto h-auto"
        height={0}
        width={300}
        src="/man.jpg"
        priority
        alt=""
      />
    </div>
  );
};

Also, even a client component first renders on the server with Next.js to improve initial load. And if you are worrying SEO, checkout this post.

Upvotes: 5

Related Questions