When using server actions to query data, you may need to explicitly refetch that data when it becomes stale. This can be achieved using the React QueryuseQueryClient hook.
For more insight on queryKeys and useQueryClient, we recommend looking
towards the React Query
Docs
In order to maintain typesafety while refetching data, we recommend placing a QueryKeyFactory in your @/lib/hooks/server-actions-hooks.ts. Here is an example
Next, we will define a server action that fetches the data. In this example, we'll create an action that simply returns a random number:
actions.ts
"use server"import { createServerAction } from "zsa"import z from "zod"export const getRandomNumber = createServerAction() .input( z .object({ min: z.number(), max: z.number(), }) ) .handler(async ({ input, ctx }) => { await new Promise((r) => setTimeout(r, 500)) return { number: Math.floor(Math.random() * (input.max - input.min)) + input.min, } })
This action takes a min and max value as input, validates that min is less than max, and returns a random number between those values after a simulated .5-second delay.
Next, use the useServerActionQuery hook to call the action in a client component:
random-number-display.tsx
"use client"import { useServerActionQuery } from "@/lib/hooks/server-action-hooks"import { getRandomNumber } from "./actions"export default function RandomNumberDisplay() { const { isLoading, isRefetching, isSuccess, data } = useServerActionQuery(getRandomNumber, { input: { min: 0, max: 100, }, queryKey: ['getRandomNumber'], //this is now typesafe due to our QueryKeyFactory }) return ( <Card className="not-prose"> <CardHeader> <CardTitle>Random number</CardTitle> <CardDescription> This fetches a random number upon mounting </CardDescription> </CardHeader> <CardContent className="flex flex-col gap-4"> <p>Random number:</p> {isSuccess && ( <>{JSON.stringify(data.number)}</> )} {isLoading ? " loading..." : ""} {isRefetching ? " refetching..." : ""} </CardContent> </Card> )}
The useServerActionQuery hook is called with the getRandomNumber action and an options object containing the input values and a queryKey. The component displays the random number returned by the action.
To manually refetch the data, use the useQueryClient hook in another component:
random-number-refetch.tsx
"use client"import { QueryKeyFactory } from "@/lib/hooks/server-action-hooks"import { useQueryClient } from "@tanstack/react-query"export default function RandomNumberRefetch() { const queryClient = useQueryClient() return ( <Card className="p-4 w-full "> <Button onClick={() => { queryClient.refetchQueries({ queryKey: QueryKeyFactory.getRandomNumber(), //return the same query key as defined in our factory }) }} className="w-full" > refetch </Button> </Card> )}
This component renders a button that, when clicked, calls the refetch function with the same queryKey used in the RandomNumberDisplay component. This will cause the server action to be re-executed and the data to be refreshed.
Random number
This fetches a random number upon mounting
Random number:
loading...
Refetch Button:
By using the useQueryClient hook in conjunction with a QueryKeyFactory, you can easily refetch data when needed in your application.
For more info on how queryKeys work, check out the React Query Docs