-
Notifications
You must be signed in to change notification settings - Fork 50.8k
Description
Server functions work on event handler props in Server Components — is this intended?
This isn't a bug report - the behaviour described below works correctly. I'm raising this to ask whether it's intended/stable behaviour and whether it should be documented.
React version: 19
Steps To Reproduce
- Create a Server Component (no
"use client"directive) - Define a server function (
"use server") inside it - Pass the server function directly to an event handler prop on a native element (e.g.,
onClick,onMouseEnter) - Trigger the event — it sends a POST request to the server and executes the function
Link to code example:
// This is a Server Component (no "use client" directive)
export default function Page() {
async function handleClick() {
"use server";
console.log("click");
}
async function handleHover() {
"use server";
console.log("hovering...");
}
return (
<div>
<button onClick={handleClick}>Click me</button>
<h2 onMouseEnter={handleHover}>Hover me</h2>
</div>
);
}Note: Passing the event object to the server function (e.g., onClick={(e) => handleClick(e)}) errors out, presumably because the event cannot be serialized. The bare function reference works fine.
Tested across
| Framework | React | Result |
|---|---|---|
| Next.js 16 | 19 | ✅ Works |
| Next.js 15.5.9 | 19 | ✅ Works |
| Next.js 14.2.35 | 18 | ❌ Crashes: "Only plain objects, and a few built-ins, can be passed to Server Actions" |
| Waku | 19 | ✅ Works (independently confirmed) |
The fact that this works on both Next.js and Waku suggests it's a React-level behaviour, not framework-specific.
The current behaviour
Server functions can be passed directly to any event handler prop on native elements inside Server Components. Both handlers fire POST requests to the server. This works on React 19 but crashes on React 18.
The expected behaviour
Based on the current documentation, I would expect this to require a Client Component wrapper. The documented patterns for server functions on native elements are:
- As a form
actionprop - Passed as a prop to a Client Component, which then attaches it to an event handler
The Server Functions reference shows server functions used with onClick, but always through a Client Component wrapper that calls () => onClick(). The Server Components docs still state: "To add interactivity, compose them with Client Components."
What I think is happening
The React 19 serializer handles server function references on any event handler prop via isServerReference. In React 18, the serializer attempted to serialize the event object and failed. In React 19, the event appears to be discarded and the server function is invoked directly.
This seems like an emergent byproduct of the serializer doing its job rather than a deliberate feature - but it's a useful pattern and it would be helpful to know:
- Is this intended and stable behavior?
- If so, should the docs be updated to reflect it?
- If not, is it likely to change in future versions?