useServerAction

useServerAction is a custom React hook provided by ZSA that allows you to easily execute server actions from your client components. It handles the execution, transition, loading states, optimistic updates, and error handling for you.

Basic Example

increment-example.tsx
"use client"

import { incrementNumberAction } from "./actions";
import { useServerAction } from "zsa-react";
import { useState } from "react";
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from "ui";

export default function IncrementExample() {
    const [counter, setCounter] = useState(0);
    const { isPending, execute, data, error, isError } = useServerAction(incrementNumberAction); 

    return (
        <Card>
            <CardHeader>
                <CardTitle>Increment Number</CardTitle>
            </CardHeader>
            <CardContent className="flex flex-col gap-4">
                <Button
                  disabled={isPending}
                  onClick={async () => {
                      const [data, err] = await execute({ 
                          number: counter, 
                      }) 

                      if (err) {
                          // handle error
                          return
                      }

                      setCounter(data);
                  }}
                >
                  Invoke action
                </Button>
                <p>Count:</p>
                <div>{isPending ? "saving..." : data}</div>
                {isError && error.code === "INPUT_PARSE_ERROR" && (
                  <div>{error.fieldErrors.number}</div>
                )}
            </CardContent>
        </Card>
    );
}
  • useServerAction allows you to use this server action from within your client components.
  • execute executes the server action endpoint with the typesafe input directly from onClick.

useServerAction uses the useTransition hook internally to handle the transition state of the action

Here is the result:

Increment Number

Count:

Usage

callbacks-example.tsx
import { useServerAction } from "zsa-react"
import { myServerAction } from "./actions"

function MyComponent() {
  const {
    data,
    isPending,
    isOptimistic,
    isError,
    error,
    isSuccess,
    status,
    execute,
    setOptimistic,
    reset,
  } = useServerAction(myServerAction, {
    onError: ({ err }) => {
      console.error("Server action error:", err)
    },
    onSuccess: ({ data }) => {
      console.log("Server action success:", data)
    },
    onStart: () => {
      console.log("Server action started")
    },
    onFinish: ([data, err]) => {
      console.log("Server action finished (invoked after onError/onSuccess)")
    },
    initialData: {/* ... */},
    retry: {
      maxAttempts: 3,
      delay: 1000, // or (currentAttempt, err) => { /* ... */ }
    },
  })

  return (
    <div>
      <button onClick={() => execute({ /* input */ })}>Execute</button>
      {isPending && <div>Loading...</div>}
      {isError && <div>Error: {error.message}</div>}
      {isSuccess && <div>Data: {JSON.stringify(data)}</div>}
    </div>
  )
}

Parameters

  • serverAction: A server action created using createServerAction.
  • opts (optional): An object containing additional options:
    • onError: A callback function to be called when the server action encounters an error. It receives an object with the err property containing the error.
    • onSuccess: A callback function to be called when the server action succeeds. It receives an object with the data property containing the returned data.
    • onStart: A callback function to be called when the server action starts executing.
    • onFinish: A callback function to be called when the server action finishes executing, regardless of the outcome.
    • initialData: Initial data to be used as the data value before the server action is executed.
    • retry: An object specifying the retry behavior:
      • maxAttempts: The maximum number of retry attempts.
      • delay: The delay in milliseconds between retry attempts, or a function that takes the current attempt number and error, and returns the delay.
    • persistErrorWhilePending: When you want to persist the error state while the server action is pending. Default is false.
    • persistDataWhilePending: When you want to persist the data state while the server action is pending. Default is false. This is helpful if you have form fields that you want to keep filled even when there is an error on other fields.

Return Value

useServerAction returns an object with the following properties:

  • data: The data returned by the server action, or undefined if the action hasn't completed successfully yet.
  • isPending: A boolean indicating whether the server action is currently being executed.
  • isOptimistic: A boolean indicating whether the data is an optimistic update.
  • isError: A boolean indicating whether the server action encountered an error.
  • error: The error object if the server action encountered an error, or undefined otherwise.
  • isSuccess: A boolean indicating whether the server action completed successfully.
  • status: The current status of the server action: "idle", "pending", "success", or "error".
  • execute: A function to execute the server action with the provided input.
  • setOptimistic: A function to set an optimistic update for the data value. It accepts either the new data directly or a function that receives the current data and returns the new data.
  • reset: A function to reset the state of the server action to its initial values.

Retry Behavior

useServerAction supports automatic retries when the server action encounters an error. You can configure the retry behavior using the retry option:

  • maxAttempts: Specifies the maximum number of retry attempts. Default is no retries.
  • delay: Specifies the delay in milliseconds between retry attempts. It can be a fixed number or a function that takes the current attempt number and error, and returns the delay. Default is no delay.

The retry option is useful for handling temporary network issues or server errors that may resolve after a short delay.

Optimistic Updates

useServerAction allows you to set optimistic updates for the data value using the setOptimistic function. Optimistic updates let you update the UI immediately based on the expected outcome of the server action, providing a better user experience.

You can pass either the new data directly or a function that receives the current data and returns the new data to setOptimistic. If the server action fails, the optimistic update will be rolled back to the previous value.

Form Actions

useServerAction also supports executing a form action directly. This is useful when you want to handle form submissions in your React components.

To do this, you can use the executeFormAction function passed back by useServerAction hook.

First, make sure you have the input type set to "formData" in your server action.

actions.ts
"use server"

import z from "zod"
import { createServerAction } from "zsa"

export const produceNewMessage = createServerAction()
  .input(
    z.object({
      name: z.string().min(5),
    }),
    {
      type: "formData", 
    }
  )
  .handler(async ({ input }) => {
    await new Promise((resolve) => setTimeout(resolve, 500))
    return "Hello, " + input.name
  })

Then, you can use the executeFormAction function to execute the form action.

form-example.tsx
"use client"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { useServerAction } from "zsa-react"
import { produceNewMessage } from "./actions"

export const ExecuteFormAction = () => {
  const { isPending, executeFormAction, isSuccess, data, isError, error } =
    useServerAction(produceNewMessage) 

  return (
    <Card className="not-prose">
      <CardHeader>
        <CardTitle>Form Example</CardTitle>
      </CardHeader>
      <CardContent className="flex flex-col gap-4">
        <form className="flex flex-col gap-4" action={executeFormAction}>
          <Input type="text" name="name" placeholder="Enter your name..." />
          <Button className="w-full" type="submit" disabled={isPending}>
            Submit
          </Button>
          {isPending && <div>Loading...</div>}
          {isSuccess && <div>Success: {JSON.stringify(data)}</div>}
          {isError && <div>Error: {JSON.stringify(error.fieldErrors)}</div>}
        </form>
      </CardContent>
    </Card>
  )
}

Here is the result:

Form Example

That's it! You can now use useServerAction to execute server actions and handle their results in your React components with ease.