@sanity-labs/workflow-engine-explore-react
v0.3.1
Published
Sanity App SDK adapter hooks for [`@sanity-labs/workflow-engine-explore`](https://www.npmjs.com/package/@sanity-labs/workflow-engine-explore). Wires `useClient` + `useCurrentUser` from `@sanity/sdk-react` into the engine's call signatures so consumers don
Maintainers
Keywords
Readme
@sanity-labs/workflow-engine-explore-react
Sanity App SDK adapter hooks for @sanity-labs/workflow-engine-explore. Wires useClient + useCurrentUser from @sanity/sdk-react into the engine's call signatures so consumers don't have to reinvent the same boilerplate per component.
Status: 0.x POC. Minimal first cut — hooks land here as the demo apps need them. The point is to find the right shape, not to ship every variant up front.
npm install @sanity-labs/workflow-engine-explore-reactPeer deps (consumers install themselves):
@sanity/sdk-react^2.11.0react^18 || ^19
Required: a SanityApp context up the tree (so useClient and useCurrentUser resolve).
Hooks
useFireAction({ tags, onSuccess?, onError? }) → { fire, pending, error }
Wrapper around workflow.fireAction. Pulls client + current user from the SDK, manages a pending/error state, calls fire({ instanceId, taskId, action }) to execute.
import { useFireAction } from "@sanity-labs/workflow-engine-explore-react";
function ApproveButton({ instanceId, taskId }: { instanceId: string; taskId: string }) {
const { fire, pending, error } = useFireAction({
tags: ["acme-prod"],
onSuccess: () => toast.success("Approved"),
onError: (e) => toast.error(e.message),
});
return (
<>
<button
disabled={pending}
onClick={() => fire({ instanceId, taskId, action: "approve" })}
>
{pending ? "Approving…" : "Approve"}
</button>
{error && <pre className="error">{error.message}</pre>}
</>
);
}The hook uses user?.id ?? "viewer" for the actor — for real apps the SDK supplies a resolved user; the fallback is a placeholder for unauthenticated contexts.
useStartInstance({ tags, onSuccess?, onError? }) → { start, pending, error }
Wrapper around workflow.startInstance. Same pattern as useFireAction.
const { start, pending } = useStartInstance({ tags: ["acme-prod"] });
await start({
workflowId: "article-review",
subject: { kind: "document", ref: articleId },
effectsContext: { editor: currentUser.id },
});useWorkflowInstance(instanceId | null) → live query result
Live read of a single workflow instance. Backed by useQuery from @sanity/sdk-react, so it re-renders on changes.
const { data: instance, isPending } = useWorkflowInstance(instanceId);
if (isPending) return <Spinner />;
if (!instance) return <NotFound />;
return <div>Currently in stage: {instance.currentStageId}</div>;useWorkflowInstanceForSubject(subjectRef | null) → live query result
Live read of the latest non-completed workflow instance whose subject.ref matches the given doc id. Useful for "is this document under a workflow?" panels.
const { data: instance } = useWorkflowInstanceForSubject(documentId);
return instance
? <WorkflowPanel instance={instance} />
: <StartWorkflowButton documentId={documentId} />;useWorkflowTaskInbox() → { rows, …queryResult }
Live read of every active task across non-completed instances, flattened to one row per task. Drop-in source for an inbox view; filter by actor at the call site.
const { rows } = useWorkflowTaskInbox();
const mine = rows.filter((r) => isAssignedToMe(r));
return (
<ul>
{mine.map((r) => (
<li key={`${r.instanceId}-${r.taskId}`}>
{r.workflowId} → {r.currentStageId} → {r.taskId}
</li>
))}
</ul>
);Each row is { instanceId, workflowId, currentStageId, subjectRef?, taskId, status, waitUntil? }.
Tag scoping
Every write hook requires a tags: string[] field in its options — same constraint as the underlying engine. See the main engine README for the rules.
The read hooks (useWorkflowInstance, useWorkflowInstanceForSubject, useWorkflowTaskInbox) currently use plain GROQ queries — they're not tag-filtered. If multiple consumers share a workflow resource, filter on the read side too or the inbox will show other tenants' tasks. Future work: tag-aware read hooks.
License
UNLICENSED — internal Sanity labs exploration.
