Skip to main content

useMutation

Call a server-side mutation or action function. Returns both a synchronous (mutate) and an async (mutateAsync) interface.

Signature

const { mutate, mutateAsync, data, isLoading, error } = useMutation<T>(functionName: string);

Parameters

ParameterTypeRequiredDescription
functionNamestringYesName of the exported server mutation or action function

Return value

PropertyTypeDescription
mutate(args?: Record<string, unknown>) => voidFire-and-forget — does not return a Promise
mutateAsync(args?: Record<string, unknown>) => Promise<T>Async variant — returns a Promise that resolves with the result
dataT | nullResult of the last successful call
isLoadingbooleantrue while the mutation is in flight
errorstring | nullError message if the last call failed

Example: Simple fire-and-forget

import { Button, useMutation } from "@zaflun/lumio-sdk";

function RevealButton({ ruleId }: { ruleId: string }) {
const { mutate: reveal, isLoading } = useMutation("revealRule");

return (
<Button
label={isLoading ? "Revealing..." : "Reveal"}
onClick={() => reveal({ id: ruleId })}
disabled={isLoading}
/>
);
}

Example: Async with refetch

import { Button, useMutation, useQuery } from "@zaflun/lumio-sdk";

function AddRuleButton() {
const { data: rules, refetch } = useQuery("getRules");
const { mutateAsync: addRule, isLoading, error } = useMutation("addRule");

const handleAdd = async () => {
try {
await addRule({ text: "New challenge rule" });
refetch(); // refresh list after successful add
} catch (e) {
// error is also available as useMutation().error
console.error("Failed to add rule:", e);
}
};

return (
<>
{error && <Text content={error} variant="muted" />}
<Button
label={isLoading ? "Adding..." : "Add rule"}
onClick={handleAdd}
disabled={isLoading}
/>
</>
);
}

Example: Action with external API

For handler-based action() functions that call external APIs:

// server/functions.ts
import { action, v } from "@zaflun/lumio-sdk/server";

export const getScoreboard = action({
args: { sport: v.string(), league: v.string() },
handler: async (ctx, args) => {
const apiKey = ctx.secrets.get("ESPN_API_KEY");
const res = await ctx.fetch(
`https://site.api.espn.com/apis/scoreboard?sport=${args.sport}&league=${args.league}`,
{ headers: { "Authorization": `Bearer ${apiKey}` } }
);
return res.json();
},
});
const { mutateAsync: fetchScores, isLoading } = useMutation("getScoreboard");

const loadScores = async () => {
const scores = await fetchScores({ sport: "football", league: "nfl" });
// use scores...
};

editorOnly mutations

Mutations declared with editorOnly: true in the server function can only be called from the editor surface. Calling them from the layer or interactive surface returns an error:

export const deleteRule = deleteRow("rules", arg("id"), { editorOnly: true });

Rate limits

Function typeLimit
mutation()50 calls/minute per installation
action()20 calls/minute per installation

Notes

  • mutate() and mutateAsync() both set isLoading and error on the returned object
  • Only one mutation of the same name can be in flight at a time — subsequent calls queue until the previous completes
  • If mutateAsync throws, the error is also stored in the error property