Hooks API
next-safe-action provides React hooks for executing server actions from client components. All hooks are imported from next-safe-action/hooks.
useAction
The primary hook for executing server actions from client components.
import { useAction } from "next-safe-action/hooks";
const { execute, result, status, ... } = useAction(safeActionFn, callbacks?);Parameters
Prop
Type
Return object
Prop
Type
The return object is a discriminated union keyed on status and the shorthand booleans (hasSucceeded, hasErrored, etc.). Checking any discriminant narrows the result type. For example, when hasSucceeded is true, result.data is guaranteed to be Data (not Data | undefined), and result.serverError / result.validationErrors are narrowed to undefined. See the Type narrowing guide for details.
Example
"use client";
import { useAction } from "next-safe-action/hooks";
import { createUser } from "./actions";
export function CreateUserForm() {
const { execute, result, isPending, hasSucceeded, hasErrored } = useAction(createUser, {
onSuccess: ({ data }) => {
console.log("Created user:", data.id);
},
onError: ({ error }) => {
console.error("Failed:", error.serverError);
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
execute({ name: "John", email: "john@example.com" });
}}>
{/* hasErrored narrows result: data is undefined, errors may be present */}
{hasErrored && result.validationErrors?.name && (
<p>{result.validationErrors.name._errors[0]}</p>
)}
<button disabled={isPending}>
{isPending ? "Creating..." : "Create User"}
</button>
{/* hasSucceeded narrows result: data is guaranteed present */}
{hasSucceeded && <p>Created user: {result.data.id}</p>}
</form>
);
}useOptimisticAction
Execute actions with optimistic UI updates. The state updates immediately on execute and reverts if the action fails.
import { useOptimisticAction } from "next-safe-action/hooks";
const { execute, optimisticState, ... } = useOptimisticAction(safeActionFn, utils);Parameters
Prop
Type
The utils object also accepts all HookBaseOptions properties including throwOnNavigation and lifecycle callbacks. When throwOnNavigation is true, onNavigation and onSettled are not available.
Return object
Same as useAction plus:
Prop
Type
Example
"use client";
import { useOptimisticAction } from "next-safe-action/hooks";
import { toggleTodo } from "./actions";
export function TodoItem({ todo }: { todo: Todo }) {
const { execute, optimisticState } = useOptimisticAction(toggleTodo, {
currentState: todo,
updateFn: (state, input) => ({
...state,
completed: !state.completed,
}),
});
return (
<li
style={{ textDecoration: optimisticState.completed ? "line-through" : "none" }}
onClick={() => execute({ id: todo.id })}
>
{optimisticState.title}
</li>
);
}useStateAction
Execute stateful actions (defined with .stateAction()) with full lifecycle control and <form action={formAction}> support.
import { useStateAction } from "next-safe-action/hooks";
const { execute, executeAsync, formAction, result, status, reset, ... } = useStateAction(safeStatefulActionFn, opts?);Parameters
Prop
Type
opts.initResult sets the initial result before the first execution (defaults to {}).
Return object
Same as useAction plus:
Prop
Type
Example
"use client";
import { useStateAction } from "next-safe-action/hooks";
import { updateProfile } from "./actions";
export function ProfileForm() {
const { formAction, result, isPending, hasSucceeded, reset } = useStateAction(updateProfile, {
initResult: {},
onSuccess: ({ data }) => {
console.log("Profile updated:", data);
},
onError: ({ error }) => {
console.error("Failed:", error.serverError);
},
onNavigation: ({ navigationKind }) => {
console.log("Navigation:", navigationKind);
},
});
return (
<form action={formAction}>
<input name="name" placeholder="Name" />
{result.validationErrors?.name && <p>{result.validationErrors.name._errors[0]}</p>}
<button disabled={isPending}>
{isPending ? "Saving..." : "Save Profile"}
</button>
{hasSucceeded && <p>Saved!</p>}
<button type="button" onClick={reset}>Reset</button>
</form>
);
}HookBaseOptions
Configuration and lifecycle callbacks for useAction, useOptimisticAction, and useStateAction. The available callbacks depend on the throwOnNavigation value:
Prop
Type
When throwOnNavigation is true, TypeScript prevents you from passing onNavigation or onSettled callbacks. This is because the render-phase throw prevents React from committing effects, so these callbacks can never execute. See why callbacks can't fire for details.
HookCallbacks
Lifecycle callbacks shared by all hooks:
Prop
Type
HookActionStatus
Union type representing all possible hook states:
type HookActionStatus =
| "idle" // No action executed yet (or after reset)
| "executing" // Action is running on the server
| "hasSucceeded" // Action completed with data, no errors
| "hasErrored" // Action completed with errors
| "hasNavigated"; // Navigation function was calledHookShorthandStatus
Object of boolean status flags returned alongside the status string:
type HookShorthandStatus = {
isIdle: boolean;
isExecuting: boolean;
isTransitioning: boolean;
isPending: boolean; // isExecuting || isTransitioning
hasSucceeded: boolean;
hasErrored: boolean;
hasNavigated: boolean;
};See also
- Hooks guide: practical guide with patterns and examples
- Optimistic updates:
useOptimisticActionin depth - Framework navigation/errors:
onNavigationandhasNavigatedbehavior - Form actions: compare
useAction,useStateAction, and React'suseActionState - Type utilities:
InferUseActionHookReturnand related types