Skip to main content

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 conditionsvalues.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

  1. Handle loading states – Show loading indicator while fetching instance
  2. Validate UI status – Check uiStatus.ready before enabling actions
  3. Handle errors gracefully – Show user-friendly error messages
  4. Reload after actions – Refresh instance to get next node
  5. Store values properly – Use appropriate storage option for your use case
  6. Test workflows – Create test instances to verify flow

Next Steps