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
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 ; } } ;