useOptimisticAction
info
useOptimisticAction
does not wait for the action to finish execution before returning the optimistic data. It is then synced with the real result from server when the action has finished its execution. If you need to perform normal mutations, use useAction
instead.
Let's say you have some todos in your database and want to add a new one. The following example shows how you can use useOptimisticAction
to add a new todo item optimistically.
Example
- Define a new action called
addTodo
, that takes aTodo
object as input:
"use server";
import { action } from "@/lib/safe-action";
import { revalidatePath } from "next/cache";
import { z } from "zod";
const schema = z.object({
id: z.string().uuid(),
body: z.string().min(1),
completed: z.boolean(),
});
export type Todo = z.infer<typeof schema>;
let todos: Todo[] = [];
export const getTodos = async () => todos;
export const addTodo = action
.metadata({ actionName: "" })
.schema(schema)
.action(async ({ parsedInput }) => {
await new Promise((res) => setTimeout(res, 500));
todos.push(parsedInput);
// This Next.js function revalidates the provided path.
// More info here: https://nextjs.org/docs/app/api-reference/functions/revalidatePath
revalidatePath("/optimistic-hook");
return {
createdTodo: parsedInput,
};
});
- Then, in the parent Server Component, you need to pass the current todos state to the Client Component:
import { getTodos } from "./addtodo-action";
export default function Home() {
return (
<main>
{/* Here we pass current todos to the Client Component.
This is updated on the server every time the action is executed, since we
used `revalidatePath()` inside action's server code. */}
<TodosBox todos={getTodos()} />
</main>
);
}
- Finally, in your Client Component, you can use it like this:
"use client";
import { useOptimisticAction } from "next-safe-action/hooks";
import { addTodo, type Todo } from "@/app/addtodo-action";
type Props = {
todos: Todo[];
};
export default function TodosBox({ todos }: Props) {
const { execute, result, optimisticState } = useOptimisticAction(
addTodo,
{
currentState: { todos }, // gets passed from Server Component
updateFn: (state, newTodo) => {
return {
todos: [...state.todos, newTodo]
};
}
}
);
return (
<div>
<button
onClick={() => {
// Here we execute the action. The input is also passed to `updateFn` as the second argument,
// in this case `newTodo`.
execute({ id: crypto.randomUUID(), body: "New Todo", completed: false });
}}>
Add todo
</button>
{/* Optimistic state gets updated right after the `execute` call (next render), it doesn't wait for the server to respond. */}
<pre>Optimistic state: {optimisticState}</pre>
</div>
);
}
useOptimisticAction
arguments
safeActionFn
: the safe action that will be called viaexecute
orexecuteAsync
functions.utils
: object with requiredcurrentState
andupdateFn
properties and optional base utils and callbacks.currentState
is passed from the parent Server Component, andupdateFn
tells the hook how to update the optimistic state before receiving the server response.
useOptimisticAction
return object
execute
: an action caller with no return. Input is the same as the safe action you passed to the hook.executeAsync
: an action caller that returns a promise with the return value of the safe action. Input is the same as the safe action you passed to the hook.input
: the input passed to theexecute
orexecuteAsync
function.result
: result of the action after its execution.optimisticState
: the optimistic state updated right afterexecute
call (on the next render), with the behavior defined inupdateFn
.reset
: programmatically reset execution state (input
,status
andresult
).status
: string that represents the current action status.isIdle
: true if the action status isidle
.isTransitioning
: true if the transition status from theuseTransition
hook used under the hood istrue
.isExecuting
: true if the action status isexecuting
.isPending
: true if the action status isexecuting
orisTransitioning
.hasSucceeded
: true if the action status ishasSucceeded
.hasErrored
: true if the action status ishasErrored
.
For checking the action status, the recommended way is to use the isPending
shorthand property. Using isExecuting
or checking if status
is "executing"
could cause race conditions when using navigation functions, such as redirect
.
Explore a working example here.