safe-action-hooks
SKILL.md
next-safe-action React Hooks
Import
// Standard hooks
import { useAction, useOptimisticAction } from "next-safe-action/hooks";
// Deprecated — use React's useActionState directly instead
import { useStateAction } from "next-safe-action/stateful-hooks";
useAction — Quick Start
"use client";
import { useAction } from "next-safe-action/hooks";
import { createUser } from "@/app/actions";
export function CreateUserForm() {
const { execute, result, status, isExecuting, isPending } = useAction(createUser, {
onSuccess: ({ data }) => {
console.log("User created:", data);
},
onError: ({ error }) => {
console.error("Failed:", error.serverError);
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
execute({ name: formData.get("name") as string });
}}>
<input name="name" required />
<button type="submit" disabled={isPending}>
{isPending ? "Creating..." : "Create User"}
</button>
{result.serverError && <p className="error">{result.serverError}</p>}
{result.data && <p className="success">Created: {result.data.id}</p>}
</form>
);
}
useOptimisticAction — Quick Start
"use client";
import { useOptimisticAction } from "next-safe-action/hooks";
import { toggleTodo } from "@/app/actions";
export function TodoItem({ todo }: { todo: Todo }) {
const { execute, optimisticState } = useOptimisticAction(toggleTodo, {
currentState: todo,
updateFn: (state, input) => ({
...state,
completed: !state.completed,
}),
});
return (
<label>
<input
type="checkbox"
checked={optimisticState.completed}
onChange={() => execute({ todoId: todo.id })}
/>
{todo.title}
</label>
);
}
Return Value
Both useAction and useOptimisticAction return:
| Property | Type | Description |
|---|---|---|
execute(input) |
(input) => void |
Fire-and-forget execution |
executeAsync(input) |
(input) => Promise<Result> |
Returns a promise with the result |
input |
Input | undefined |
Last input passed to execute |
result |
SafeActionResult |
Last action result ({ data?, serverError?, validationErrors? }) |
reset() |
() => void |
Resets all state to initial values |
status |
HookActionStatus |
Current status string |
isIdle |
boolean |
No execution has started yet |
isExecuting |
boolean |
Action promise is pending |
isTransitioning |
boolean |
React transition is pending |
isPending |
boolean |
isExecuting || isTransitioning |
hasSucceeded |
boolean |
Last execution returned data |
hasErrored |
boolean |
Last execution had an error |
hasNavigated |
boolean |
Last execution triggered a navigation |
useOptimisticAction additionally returns:
| optimisticState | State | The optimistically-updated state |
Supporting Docs
- execute vs executeAsync, result handling
- Optimistic updates with useOptimisticAction
- Status lifecycle and all callbacks
Anti-Patterns
// BAD: Using executeAsync without try/catch when navigation errors are possible
const handleClick = async () => {
const result = await executeAsync({ id }); // Throws on redirect!
showToast(result.data);
};
// GOOD: Wrap executeAsync in try/catch
const handleClick = async () => {
try {
const result = await executeAsync({ id });
showToast(result.data);
} catch (e) {
// Navigation errors (redirect, notFound) are re-thrown
// They'll be handled by Next.js — just let them propagate
throw e;
}
};
Weekly Installs
32
Repository
next-safe-action/skillsFirst Seen
10 days ago
Security Audits
Installed on
opencode32
gemini-cli32
github-copilot32
codex32
amp32
kimi-cli32