Workflow Execution
Workflow execution involves creating instances from workflow definitions and navigating through nodes based on user input and conditions.
Creating Workflow Instances
Workflow instances are created from workflow definitions:
// Backend API call
POST /api/workflow-instances
{
workflowId: "00000000-0000-0000-0000-000000000001",
contextEntity: "Person",
contextEntityId: "00000000-0000-0000-0000-000000000002",
values: {}, // Initial values
}
Using useWorkflows Hook
The useWorkflows hook manages workflow instance state:
import { useWorkflows } from "@xond/workflow";
function WorkflowPage({ instanceId }: { instanceId: string }) {
const {
instance,
node,
nodeId,
nodeCode,
values,
uiStatus,
setUIStatus,
} = useWorkflows({
entity: "workflowInstance",
id: instanceId,
actionEndpoint: "/api/workflow-instances/action",
walkEndpoint: "/api/workflow-instances/walk",
includes: ["node", "workflow"],
});
// instance - Current workflow instance
// node - Current node being executed
// values - Merged values from all previous nodes
// uiStatus - UI form status (ready, data)
}
Rendering Workflow UI
Use WorkflowUI component to render the current node's UI:
import { WorkflowUI } from "@xond/workflow";
import { useState } from "react";
function WorkflowPage({ instanceId }: { instanceId: string }) {
const { instance, node, values, uiStatus, setUIStatus } = useWorkflows({
entity: "workflowInstance",
id: instanceId,
});
const [updatedData, setUpdatedData] = useState<any>(null);
const handleReturnValue = (value: { ready: boolean; data: any }) => {
setUpdatedData(value);
setUIStatus(value);
};
if (!node || !instance) {
return <div>Loading...</div>;
}
return (
<WorkflowUI
currentNode={node}
workflowInstance={instance}
returnValue={handleReturnValue}
/>
);
}
Handling Actions
Use WorkflowActions component to render action buttons:
import { WorkflowActions } from "@xond/workflow";
function WorkflowPage({ instanceId }: { instanceId: string }) {
const { instance, node, values, uiStatus } = useWorkflows({
entity: "workflowInstance",
id: instanceId,
});
const [updatedData, setUpdatedData] = useState<any>(null);
const handleAction = async ({ value, handle }: { value: any; handle: string }) => {
// Call API to proceed to next node
const response = await fetch("/api/workflow-instances/action", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
instanceId,
handle,
value,
}),
});
if (response.ok) {
// Reload instance to get next node
window.location.reload();
} else {
alert("Failed to proceed");
}
};
return (
<>
<WorkflowUI
currentNode={node}
workflowInstance={instance}
returnValue={(value) => setUpdatedData(value)}
/>
<WorkflowActions
currentNode={node}
workflowInstance={instance}
oldValue={values}
updatedDataFromUI={updatedData}
callBack={handleAction}
/>
</>
);
}
Complete Example
Here's a complete workflow execution page:
import { WorkflowUI, WorkflowActions, useWorkflows } from "@xond/workflow";
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
function WorkflowExecutionPage() {
const { instanceId } = useParams<{ instanceId: string }>();
const {
instance,
node,
values,
uiStatus,
setUIStatus,
} = useWorkflows({
entity: "workflowInstance",
id: instanceId || "",
actionEndpoint: "/api/workflow-instances/action",
walkEndpoint: "/api/workflow-instances/walk",
includes: ["node", "workflow"],
});
const [updatedData, setUpdatedData] = useState<any>(null);
const handleReturnValue = (value: { ready: boolean; data: any }) => {
setUpdatedData(value);
setUIStatus(value);
};
const handleAction = async ({ value, handle }: { value: any; handle: string }) => {
try {
const response = await fetch("/api/workflow-instances/action", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
instanceId,
handle,
value,
}),
});
if (!response.ok) {
throw new Error("Failed to proceed");
}
// Reload to get next node
window.location.reload();
} catch (error) {
console.error("Error executing action:", error);
alert("Failed to proceed. Please try again.");
}
};
if (!instance || !node) {
return (
<div className="flex h-screen items-center justify-center">
<div>Loading workflow...</div>
</div>
);
}
return (
<div className="container mx-auto max-w-4xl p-4">
<div className="mb-4">
<h1 className="text-2xl font-bold">{instance.workflow.name}</h1>
<p className="text-gray-600">{instance.workflow.description}</p>
</div>
<div className="rounded-lg border bg-white p-6 shadow">
<WorkflowUI
currentNode={node}
workflowInstance={instance}
returnValue={handleReturnValue}
/>
</div>
<div className="mt-4">
<WorkflowActions
currentNode={node}
workflowInstance={instance}
oldValue={values}
updatedDataFromUI={updatedData}
callBack={handleAction}
/>
</div>
</div>
);
}
export default WorkflowExecutionPage;
Workflow Values
Values are merged from all previous nodes:
// Node 1 collects: { name: "John", email: "john@example.com" }
// Node 2 collects: { phone: "1234567890" }
// Node 3 has access to: { name: "John", email: "john@example.com", phone: "1234567890" }
Accessing Values
Values are accessible in:
- Decision node conditions –
values.person.name - Form default values – Pre-filled from previous nodes
- Action callbacks – Passed to action handlers
Copying Values to Root
Use copyToRoot: true in node configuration to make values available to all subsequent nodes:
{
code: "personal-data",
copyToRoot: true, // Makes values available to all nodes
// ...
}
Storage Options
Workflow instances can be stored in:
Memory Storage
{
contextStorage: "MEMORY_STORAGE",
}
Local Storage
{
contextStorage: "LOCAL_STORAGE",
}
Stored in browser's localStorage with key: workflow.code
Database Storage
{
contextStorage: "DATABASE_STORAGE",
}
Stored in database via API calls.
Backend Integration
Action Endpoint
Handle workflow actions:
POST /api/workflow-instances/action
{
instanceId: string,
handle: string,
value: any,
}
Response:
{
success: boolean,
instanceId: string,
currentNodeId: string,
}
Walk Endpoint
Execute workflow walk (for automatic nodes):
POST /api/workflow-instances/walk
{
instanceId: string,
}
Response:
{
success: boolean,
currentNodeId: string,
completed: boolean,
}
Error Handling
Handle errors during execution:
const handleAction = async ({ value, handle }: { value: any; handle: string }) => {
try {
const response = await fetch("/api/workflow-instances/action", {
method: "POST",
body: JSON.stringify({ instanceId, handle, value }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to proceed");
}
window.location.reload();
} catch (error) {
console.error("Workflow action error:", error);
// Show error notification
alert(error.message);
}
};
Best Practices
- Handle loading states – Show loading indicator while fetching instance
- Validate UI status – Check
uiStatus.readybefore enabling actions - Handle errors gracefully – Show user-friendly error messages
- Reload after actions – Refresh instance to get next node
- Store values properly – Use appropriate storage option for your use case
- Test workflows – Create test instances to verify flow
Next Steps
- Learn about custom forms
- Explore integration patterns
- See node configuration