Examples

Complex Workflow Example

Example of a complex workflow with multiple stores, related data, and coordinated operations.

Scenario

A project management system where:

  • Projects have multiple tasks
  • Tasks have assignments
  • Creating a project creates default tasks
  • Deleting a project cascade-deletes tasks

Project Store

// hooks/use-project-store.ts
export function useProjectStore() {
  return useStore<Project>({
    datasourceId: 'WKProjects',
    page: 'project-page',
    alias: 'project-list',
    limit: 20,
    includeCount: true,
    autoQuery: true,
    sort: { projectName: 1 },
  });
}
// hooks/use-project-tasks-store.ts
export function useProjectTasksStore(projectId: string) {
  return useStore<WKProjectTasks>({
    datasourceId: 'WKProjectTasks',
    page: 'project-tasks-page',
    alias: `project-tasks-${projectId}`,
    limit: 100,
    autoQuery: false,
    match: { projectId },
    sort: { taskName: 1 },
  });
}

Coordinated Operations

Create Project with Default Tasks

const handleCreateProject = useCallback(async () => {
  // Create project
  projectStore.createNew({
    partialRecord: {
      projectName: 'New Project',
      status: 'draft',
    },
  });
  
  await projectStore.save({ feedback: 'Project created' });
  const projectId = projectStore.currentRowId();
  
  if (projectId) {
    // Create default tasks
    const defaultTasks = [
      { taskName: 'Task 1', projectId },
      { taskName: 'Task 2', projectId },
    ];
    
    for (const task of defaultTasks) {
      tasksStore.createNew({ partialRecord: task });
    }
    
    await tasksStore.save({ feedback: 'Default tasks created' });
  }
}, [projectStore, tasksStore]);

Cascade Delete

const handleDeleteProject = useCallback(async (project: Project) => {
  const confirmed = await confirmWithUser({
    title: 'Delete Project',
    content: `Are you sure? This will delete all tasks and assignments.`,
  });
  
  if (confirmed) {
    // Delete project (cascade deletes tasks via afterDelete hook)
    projectStore.deleteRow(project.projectId);
    await projectStore.save({ feedback: 'Project deleted' });
    
    // Refresh tasks store (tasks were cascade-deleted by DB)
    tasksStore.refresh();
  }
}, [projectStore, tasksStore]);

Store Invalidation

// When project is saved, refresh related stores
const projectStore = useStore<Project>({
  datasourceId: 'WKProjects',
  alias: 'project-list',
  invalidateStoresOnSave: [
    { datasourceId: 'WKProjectTasks', alias: 'project-tasks-all' },
  ],
});

Multi-Store Form

function ProjectForm({ projectStore, tasksStore }: Props) {
  const project = useCurrentRowSync(projectStore);
  const tasks = useDBRows(tasksStore);

  return (
    <div className="grid gap-6">
      {/* Project fields */}
      <div className="grid gap-4">
        <TextInput
          label="Project Name"
          value={project?.projectName || ''}
          onChange={(value) => projectStore.setValue('projectName', value)}
        />
      </div>

      {/* Tasks list */}
      <div>
        <h3>Tasks</h3>
        <ul>
          {tasks.map((task) => (
            <li key={task._id}>{task.taskName}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

Key Patterns

  1. Separate stores for related data - Use different aliases
  2. Store invalidation - Refresh related stores on save
  3. Cascade operations - Handle in DataSource hooks
  4. Coordinated saves - Save parent, then children

Next Steps

Previous
Filtered Table