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 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​

  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​