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
| Parameter | Type | Required | Description |
|---|---|---|---|
functionName | string | Yes | Name of the exported server mutation or action function |
Return value
| Property | Type | Description |
|---|---|---|
mutate | (args?: Record<string, unknown>) => void | Fire-and-forget — does not return a Promise |
mutateAsync | (args?: Record<string, unknown>) => Promise<T> | Async variant — returns a Promise that resolves with the result |
data | T | null | Result of the last successful call |
isLoading | boolean | true while the mutation is in flight |
error | string | null | Error 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 type | Limit |
|---|---|
mutation() | 50 calls/minute per installation |
action() | 20 calls/minute per installation |
Notes
mutate()andmutateAsync()both setisLoadinganderroron 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
mutateAsyncthrows, the error is also stored in theerrorproperty