Server actions
Server actions are async functions that can be called from the client-side. You don't have to implement API endpoints for these functions to be callable as the client can call these functions like a direct reference to the function itself. Server actions are usable on forms, buttons, submit inputs, and as props to client components. React and the framework will manage calling these functions on the server-side.
You can expose any "use server";
marked function as a server action. Server actions can be called from the client using the action
prop on a <form>
element, formAction
on a <button>
or <input>
element, or from a client component passing down the server action function as a prop to the client component.
If you use TypeScript, all of your server actions are type-safe and you will get type errors if you pass the wrong parameters to a server action or if you try to access a property on the response object that doesn't exist.
You can define server actions inline in your components as any other event handler in a React component.
export default function App() {
async function action() {
"use server";
console.log("Server action called!");
}
return (
<form action={action}>
<button type="submit">Submit</button>
</form>
);
}
Progressive enhancement: if you have JavaScript enabled, the server action will be called using the
fetch
API and the response will be used to update the DOM in a React transition. If you don't have JavaScript enabled, the server action will be called using a regular HTTP request.
You can even define your server actions inline your JSX.
export default function App() {
return (
<form
action={async () => {
"use server";
console.log("Server action called!");
}}
>
<button type="submit">Submit</button>
</form>
);
}
Server actions are able to access all variables in scope, including references to props and any variables available in the scope of the server action function from the last render of the component as server action functions are mapped on each render of the server component.
If you want to keep your server actions in separate modules, you can do so by using the "use server";
pragma at the top of your module. All exported functions from a server action module will be usable as server actions.
"use server";
export async function action() {
console.log("Server action called!");
}
You will get all form data as an object as the first parameter to your server action function.
export default function App() {
async function action(formData) {
"use server";
console.log(`Server action called by ${formData.get("name")}!`);
}
return (
<form action={action}>
Your name: <input name="name" />
<button type="submit">Submit</button>
</form>
);
}
import { useActionState } from "@lazarv/react-server/router";
export default function App() {
async function action(formData) {
"use server";
console.log(`Server action called by ${formData.get("name")}!`);
}
const { error } = useActionState(action);
return (
<form action={action}>
Your name: <input name="name" />
<button type="submit">Submit</button>
{error && <p>{error.message}</p>}
</form>
);
}
To access the action state, you can use the useActionState
hook. The useActionState
hook takes the server action function as the first parameter and returns an object with the following properties:
formData
: the form data objectdata
: the data object returned by the server actionerror
: the error object if the action failedactionId
: the action ID of the current action
You can also pass server action function references to client components as props and call them from the client component as any other async function.
"use client";
export default function MyClientComponent({ action }) {
const handleClick = () => {
action({ name: "John" });
};
return <button onClick={handleClick}>Click me!</button>;
}
import MyClientComponent from "./MyClientComponent";
export default function App() {
async function action({ name }) {
"use server";
console.log(`Server action called by ${name}!`);
}
return (
<div>
<MyClientComponent action={action} />
</div>
);
}
Server actions called from client components can return data to the client component directly. This can be useful if you want to display a message to the user after the server action has been completed or if you want to display an error message if the server action failed.
"use client";
export default function MyClientComponent({ action }) {
const [response, setResponse] = useState(null);
const handleClick = async () => {
const response = await action({ name: "John" });
setResponse(response);
};
return (
<>
<button onClick={handleClick}>Click me!</button>
{response && <p>{response.message}</p>}
</>
);
}
import MyClientComponent from "./MyClientComponent";
export default function App() {
async function action({ name }) {
"use server";
console.log(`Server action called by ${name}!`);
return { message: `Hello ${name}!` };
}
return (
<div>
<MyClientComponent action={action} />
</div>
);
}