Skip to content

Solving Next.js "Event handlers cannot be passed to Client Component props" Error

Context

This error occurs when using event handlers like onClick in Next.js 13+ components due to the App Router's default Server Component architecture. Server Components render on the server where event handlers cannot execute. This article explains solutions with code examples.

Problem Statement

In Next.js 13+, all components are Server Components by default. Server Components:

  • Execute only on the server
  • Cannot handle client-side interactivity
  • Lack browser APIs like DOM event handlers

When you try to attach an event handler to a component prop:

tsx
<button onClick={handleClick}>Request Help</button>

You'll encounter the error:

Event handlers cannot be passed to Client Component props.
^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.

Key Constraints

  • Server Components execute only on the server before the page reaches the browser
  • Client-side interactions requires components to hydrate in the browser
  • Only Client Components support event handlers like onClick

Solution 1: Convert to Client Component

Add the 'use client' directive at the top of your file to convert the entire component to a Client Component:

tsx
'use client'; // Client Component directive

const YourComponent = () => {
  const handleClick = () => {
    // Client-side logic
  };

  return (
    <button onClick={handleClick}>Request Help</button>
  );
};

export default YourComponent;

When to Use This Approach:

  • Component is entirely interactive (buttons, forms)
  • Requires browser-state or effects
  • Uses client-side libraries like SweetAlert

Tradeoffs

  • Client Components lose server component benefits like zero-bundle-size
  • They cannot contain Server Components as children
  • Not suitable for pure server-rendered content

Solution 2: Extract Interactive Elements

Keep your main component as a Server Component by extracting interactive elements into smaller Client Components:

tsx
'use client'; // Child component is client-only

const HelpButton = () => {
  const handleClick = () => { /* ... */ };
  
  return (
    <button onClick={handleClick}>Request Help</button>
  );
};

Then import into your Server Component:

tsx
import HelpButton from './HelpButton';

const ServerComponent = async () => {
  // Server-side data fetching
  const data = await fetchData();
  
  return (
    <div>
      <h1>{data.title}</h1>
      {/* Interactive part isolated */}
      <HelpButton />
    </div>
  );
};

Benefits of This Pattern:

AdvantageDescription
GranularityIsolates interactivity to specific UI elements
PerformanceMaximizes server rendering for static content
Data FetchingMaintains async server data fetching

Composition Pattern

This matches Next.js recommended patterns for mixing server and client components.

Solution 3: Handle Server Actions

For form submissions or database mutations, use Server Actions:

Pattern 1: Direct Form Action

tsx
async function submitForm(data: FormData) {
  'use server'; // Server Action directive
  // Server-side data processing
}

// Client Component
export default function FormPage() {
  return (
    <form action={submitForm}>
      <input name="email" />
      <button>Submit</button>
    </form>
  );
}

Pattern 2: Bound Action Handlers

tsx
import { serverAction } from '@/actions';

const ClientButton = ({ id }: { id: string }) => {
  const boundAction = serverAction.bind(null, id);
  
  return (
    <button onClick={() => boundAction()}>
      Execute
    </button>
  );
};
When to Use Server Actions
  • Data mutations requiring database access
  • Form submissions with server validation
  • Operations needing API route credentials
  • Avoid using for trivial UI interactions

When navigation is the goal, replace onClick handlers with next/link:

tsx
import Link from 'next/link';

// In Server Component
const NavigationExample = () => (
  <Link href="/help-page">
    <button>Get Help</button>
  </Link>
);

Best Practices Summary

tsx
// Keep server components server-only
export default async function ProductPage() {
  const product = await fetchProduct();
  
  return (
    <>
      <ProductDetails data={product} /> // Server rendered
      <BuyButton /> // Client component
    </>
  );
}
tsx
❌ Mixing server/client logic in same component
export default function ProductPage() {
  const [cartItems, setCartItems] = useState([]); // ❌ Invalid in Server Component
  
  return <button onClick={...}>Add to Cart</button>; // ❌ Event handler
}

Key Takeaways

  1. Identify Component Needs - Is interaction required? If not, keep as Server Component
  2. Use 'use client' Judiciously - Apply only where interactivity is required
  3. Leverage Composition - Blend server-rendered content with client interactive elements
  4. Prioritize Server Actions - For data mutations instead of client API routes
  5. Verify Directives - Ensure 'use client' is the first line in Client Components

For complex scenarios: