next-safe-action
API reference

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 called

HookShorthandStatus

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

On this page