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:
<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:
'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:
'use client'; // Child component is client-only
const HelpButton = () => {
const handleClick = () => { /* ... */ };
return (
<button onClick={handleClick}>Request Help</button>
);
};
Then import into your Server Component:
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:
Advantage | Description |
---|---|
Granularity | Isolates interactivity to specific UI elements |
Performance | Maximizes server rendering for static content |
Data Fetching | Maintains 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
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
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
Alternative: Use Links for Navigation
When navigation is the goal, replace onClick
handlers with next/link
:
import Link from 'next/link';
// In Server Component
const NavigationExample = () => (
<Link href="/help-page">
<button>Get Help</button>
</Link>
);
Best Practices Summary
// Keep server components server-only
export default async function ProductPage() {
const product = await fetchProduct();
return (
<>
<ProductDetails data={product} /> // Server rendered
<BuyButton /> // Client component
</>
);
}
❌ 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
- Identify Component Needs - Is interaction required? If not, keep as Server Component
- Use
'use client'
Judiciously - Apply only where interactivity is required - Leverage Composition - Blend server-rendered content with client interactive elements
- Prioritize Server Actions - For data mutations instead of client API routes
- Verify Directives - Ensure
'use client'
is the first line in Client Components
For complex scenarios: