Loading...
Loading...
Use when executing next-safe-action actions from React client components -- useAction, useOptimisticAction, handling status/callbacks (onSuccess/onError/onSettled), execute vs executeAsync, or optimistic UI updates
npx skill4agent add next-safe-action/skills safe-action-hooks// 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";"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>
);
}"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>
);
}useActionuseOptimisticAction| Property | Type | Description |
|---|---|---|
| | Fire-and-forget execution |
| | Returns a promise with the result |
| | Last input passed to execute |
| | Last action result ( |
| | Resets all state to initial values |
| | Current status string |
| | No execution has started yet |
| | Action promise is pending |
| | React transition is pending |
| | |
| | Last execution returned data |
| | Last execution had an error |
| | Last execution triggered a navigation |
useOptimisticActionoptimisticStateState// 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;
}
};