Patterns
CRUD Operations
Complete patterns for Create, Read, Update, and Delete operations using Stores.
Create
Initialize Store Before Opening Dialog
// ✅ Correct
const handleAdd = useCallback(() => {
store.createNew({
partialRecord: { status: 'draft' },
});
setIsDialogOpen(true);
}, [store]);
// ❌ Wrong - timing issues
useEffect(() => {
if (open) {
store.createNew({ partialRecord: { status: 'draft' } });
}
}, [open, store]);
Save New Record
const handleSave = async () => {
const success = await store.save({ feedback: 'Entity created' });
if (success) {
onClose();
}
};
Read
List Page
const store = useStore<Entity>({
datasourceId: 'Entity',
page: 'entity-page',
alias: 'entity-list',
limit: 20,
includeCount: true,
autoQuery: true, // Auto-fetch on mount
});
// Display in PageLayoutTemplate
<PageLayoutTemplate
store={store}
tableColumns={tableColumns}
// ...
/>
Detail Page
const store = useStore<Entity>({
datasourceId: 'Entity',
page: 'entity-detail-page',
alias: 'entity-edit',
limit: 1,
autoQuery: false,
});
useEffect(() => {
store.executeQuery({
query: { match: { id } },
force: true,
});
}, [id, store]);
const row = useCurrentRow(store);
Update
Edit Existing Record
const handleEdit = useCallback((row: Entity) => {
store.setCurrentRow(row);
setIsDialogOpen(true);
}, [store]);
Update Fields
// Update current row
store.setValue('name', 'New Name');
// Update specific row
store.setValue('name', 'New Name', rowId);
// Update multiple fields
store.updateRow(rowId, {
name: 'New Name',
status: 'active',
});
Save Changes
const handleSave = async () => {
const success = await store.save({ feedback: 'Entity updated' });
if (success) {
onClose();
}
};
Delete
Delete with Confirmation
import { confirmWithUser } from '@wayvo-ai/core/ui';
const handleDelete = useCallback(async (entity: Entity) => {
const confirmed = await confirmWithUser({
title: 'Delete Entity',
content: `Are you sure you want to delete "${entity.name}"? This action cannot be undone.`,
});
if (confirmed) {
store.deleteRow(entity.id);
await store.save({ feedback: 'Entity deleted' });
}
}, [store]);
Delete Without Confirmation
const handleDelete = useCallback(async (entity: Entity) => {
store.deleteRow(entity.id);
await store.save({ feedback: 'Entity deleted' });
}, [store]);
Complete Example
'use client';
import { useCallback, useState } from 'react';
import { Popup, useCurrentRowSync, useIsStoreDirty, useIsStorePosting, confirmWithUser } from '@wayvo-ai/core/ui';
import type { Store } from '@wayvo-ai/core/common';
import type { Entity } from '@/lib/common/ds/types/module/Entity';
import { TextInput } from '@wayvo-ai/core/ui';
export function EntityDialog({ open, onOpenChange, store, editingRow }: Props) {
const row = useCurrentRowSync(store);
const isDirty = useIsStoreDirty(store);
const isPosting = useIsStorePosting(store);
const handleSave = async () => {
try {
const success = await store.save();
if (success) {
showSuccess(editingRow ? 'Updated' : 'Created');
onOpenChange(false);
}
} catch {
showError('Failed to save');
}
};
const handleClose = () => {
store.resetStore();
onOpenChange(false);
};
if (!open) return null;
return (
<Popup
title={editingRow ? 'Edit Entity' : 'Add Entity'}
onClose={handleClose}
width={520}
height={430}
footer={
<>
<Button variant="outline" onClick={handleClose} disabled={isPosting}>
Cancel
</Button>
<Button onClick={handleSave} disabled={isPosting || !isDirty || !row}>
Save
</Button>
</>
}
>
{row && (
<div className="grid gap-4">
<TextInput
label="Name"
value={row.name || ''}
onChange={(value) => store.setValue('name', value)}
required
/>
</div>
)}
</Popup>
);
}
Important Notes
- After save() - Store auto-refreshes, no need to call
executeQuery()orrefresh() - After deleteRow() - Always call
save()to persist the deletion - Dialog initialization - Initialize store BEFORE opening, not in useEffect
- Use useCurrentRowSync - For forms to prevent cursor jumping