WorkRunner Documentation

React Hooks

The Construct SDK provides React hooks for data persistence, PWA features, and more.

import {
  useConstructData,
  useDataStore,
  useOnlineStatus,
  usePWAInstall,
  useIsInstalledPWA,
  useImportExport,
  useInterAppComm,
  useRuntime,
} from "@useworkapp/construct-sdk";

useConstructData

The primary hook for persisting data with IndexedDB and optional cloud sync.

useConstructData<T>(key: string, defaultValue: T, options?: Options)

// Basic usage
const [todos, setTodos] = useConstructData<Todo[]>("todos", []);

// With cloud sync
const [data, setData, { status, isLoading, sync }] = useConstructData(
  "my-data",
  defaultValue,
  { sync: true }
);

// Options
interface Options {
  sync?: boolean;          // Enable cloud sync (default: false)
  syncDebounce?: number;   // Debounce sync in ms (default: 1000)
}

// Return metadata
{
  status: 'local' | 'syncing' | 'synced' | 'offline' | 'error';
  isLoading: boolean;
  error: Error | null;
  sync: () => Promise<void>;   // Manual sync trigger
  version: number;             // Data version
  lastSyncedAt: Date | null;   // Last sync timestamp
}

useDataStore

Advanced data store with schema validation and query capabilities.

useDataStore<T>(config: StoreConfig)

interface StoreConfig<T> {
  name: string;
  schema: {
    [K in keyof T]: {
      type: 'string' | 'number' | 'boolean' | 'object' | 'array';
      required?: boolean;
    };
  };
}

const [store, { isLoading, error }] = useDataStore<Item>({
  name: "items",
  schema: {
    id: { type: "string", required: true },
    name: { type: "string", required: true },
    count: { type: "number" },
  },
});

// CRUD operations
await store.create(item);
await store.read(id);
await store.update(id, partial);
await store.delete(id);
await store.getAll();

// Query with operators
const results = await store.query({
  name: { contains: "search" },
  count: { gte: 10 },
});

// Query operators
{ eq: value }      // equals
{ ne: value }      // not equals
{ gt: value }      // greater than
{ gte: value }     // greater than or equal
{ lt: value }      // less than
{ lte: value }     // less than or equal
{ in: [values] }   // in array
{ contains: str }  // string contains

useOnlineStatus

Track the user's online/offline status reactively.

useOnlineStatus(): boolean

const isOnline = useOnlineStatus();

return (
  <div>
    {!isOnline && (
      <Banner variant="warning">
        You're offline. Changes will sync when you reconnect.
      </Banner>
    )}
  </div>
);

usePWAInstall

Handle PWA installation prompts for standalone Constructs.

usePWAInstall(): { canInstall: boolean, install: () => Promise<boolean>, dismiss: () => void }

const { canInstall, install, dismiss } = usePWAInstall();

return (
  <>
    {canInstall && (
      <div className="fixed bottom-4 right-4 p-4 bg-card rounded-lg shadow-lg">
        <p>Install this app for offline access?</p>
        <div className="flex gap-2 mt-2">
          <Button onClick={install}>Install</Button>
          <Button variant="ghost" onClick={dismiss}>Not now</Button>
        </div>
      </div>
    )}
  </>
);

useIsInstalledPWA

Detect if the Construct is running as an installed PWA.

useIsInstalledPWA(): boolean

const isInstalled = useIsInstalledPWA();

// Show different UI for installed vs browser
return (
  <header>
    {!isInstalled && <Button onClick={install}>Install App</Button>}
    {isInstalled && <span>Running as standalone app</span>}
  </header>
);

useImportExport

Export and import user data in JSON or CSV format.

useImportExport(): { exportData: (opts) => Promise<Blob>, importData: (opts) => Promise<void> }

const { exportData, importData } = useImportExport();

// Export data
const handleExport = async () => {
  const blob = await exportData({
    format: "json",           // "json" | "csv"
    stores: ["todos"],        // which data stores to export
    compress: true,           // gzip compression (optional)
  });

  // Create download link
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "my-data.json";
  a.click();
};

// Import data
const handleImport = async (file: File) => {
  await importData({
    file,
    onConflict: "replace",    // "skip" | "replace" | "error"
  });
};

useInterAppComm

Enable communication between Constructs using BroadcastChannel.

useInterAppComm(config): InterAppComm

const comm = useInterAppComm({
  permissions: ["read:todos", "write:todos"],  // requested permissions
});

// Send a message to other Constructs
comm.send({
  type: "TODO_CREATED",
  payload: { id: "1", text: "New todo" },
});

// Subscribe to messages
useEffect(() => {
  const unsubscribe = comm.subscribe("TODO_UPDATED", (payload) => {
    console.log("Todo updated:", payload);
  });
  return unsubscribe;
}, []);

// Request/response pattern
const response = await comm.request({
  type: "GET_TODOS",
  timeout: 5000,
});

useRuntime

Manage Construct version and handle auto-updates.

useRuntime(): RuntimeInfo

const {
  version,           // Current version
  latestVersion,     // Latest available version
  updateAvailable,   // boolean
  update,            // () => Promise<void>
  autoUpdate,        // Enable auto-updates
} = useRuntime();

return (
  <>
    {updateAvailable && (
      <Banner>
        Version {latestVersion} available.
        <Button onClick={update}>Update now</Button>
      </Banner>
    )}
  </>
);

Best Practices

Use useConstructData for simple state

It handles persistence, loading states, and sync automatically.

Use useDataStore for complex data

When you need queries, schema validation, or multiple collections.

Handle loading states

Always check isLoading before rendering data-dependent UI.

Show sync status to users

Let users know when their data is saved, syncing, or offline.

React Hooks | Runner Documentation | Work