Pitfalls

Common Pitfalls

Avoid these common mistakes when building with @wayvo-ai/core.

Store Method vs Hook Usage

❌ Don't Call Store Methods During Render

Store methods return values directly but do not subscribe to changes. When called during render, the component won't re-render when data changes.

// ❌ Wrong - won't re-render when rows change
function MyComponent({ store }: Props) {
  const rows = store.rows();           // Non-reactive!
  const isLoading = store.isLoading(); // Non-reactive!
  return <div>{rows.length} items</div>;
}

// ✅ Correct - subscribes to changes
function MyComponent({ store }: Props) {
  const rows = useDBRows(store);
  const isLoading = useIsStoreLoading(store);
  return <div>{rows.length} items</div>;
}

Exception: Store methods ARE safe in event handlers, useEffect, and async functions.

Dialog Initialization

❌ Don't Initialize Store in useEffect

// ❌ Wrong - causes timing issues
useEffect(() => {
  if (open) store.createNew({ ... });
}, [open]);

// ✅ Correct - initialize before opening
const handleAdd = () => {
  store.createNew({ ... });
  setOpen(true);
};

Query After Save

❌ Don't Call executeQuery or refresh After Save

// ❌ Wrong - save auto-refreshes
await store.save();
await store.executeQuery();

// ❌ Also wrong
await store.save();
await store.refresh();

// ✅ Correct - just save
await store.save({ feedback: 'Saved successfully' });

Form Fields

❌ Don't Use useCurrentRow for Form Fields

// ❌ Causes cursor jumping in inputs
const row = useCurrentRow(store);

// ✅ Use sync mode
const row = useCurrentRowSync(store);

Table Columns

❌ Don't Access row.original Directly

// ❌ Wrong - row.original only contains { id: "..." }
cell: ({ row }) => {
  const name = row.original.name; // undefined!
  return <span>{name}</span>;
}

// ✅ Correct - use TableCell
cell: (props) => <TableCell type="Text" attributeCode="name" {...props} />

// ✅ Alternative - use useRowValue
function NameCell({ store, rowId }: { store: Store<Entity>; rowId: string }) {
  const name = useRowValue(store, rowId, 'name');
  return <span>{name || '-'}</span>;
}

Query Deduplication

❌ Don't Add Manual Query Guards

The store handles query deduplication internally:

// ❌ Wrong - unnecessary guards
useEffect(() => {
  if (row?.projectId === projectId) return;  // Redundant
  store.executeQuery({ query: { match: { projectId } } });
}, [projectId, store, row?.projectId]);

// ✅ Correct - just call executeQuery
useEffect(() => {
  store.executeQuery({ query: { match: { projectId } } });
}, [projectId, store]);

Multiple useEffects

❌ Don't Have Multiple useEffects Calling executeQuery

// ❌ Wrong - both effects run on mount
useEffect(() => {
  if (customerId) store.setSmartSearchFilters([...]);
  store.executeQuery();
}, [customerId, store]);

useEffect(() => {
  store.executeQuery();  // Duplicate!
}, [includeArchived, store]);

// ✅ Correct - single effect
useEffect(() => {
  if (customerId) store.setSmartSearchFilters([...]);
  store.executeQuery();
}, [customerId, includeArchived, store]);

Date Handling

❌ Don't Use Local Timezone Methods

// ❌ Wrong - timezone shifts the date
const formatted = new Date(date).toLocaleDateString();
const formatted = format(new Date(date), 'MMM dd, yyyy');

// ✅ Correct - use UTC methods or utilities
const formatted = formatDateDisplay(date);

Click-Outside with Radix UI

❌ Don't Forget Portal Elements

When building click-outside handlers with Radix UI components:

// ❌ Wrong - dropdown closes when clicking options
function handleClickOutside(e: MouseEvent) {
  if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
    handleCancel();  // Fires when clicking dropdown options!
  }
}

// ✅ Correct - check for Radix portals
function handleClickOutside(e: MouseEvent) {
  if (containerRef.current?.contains(e.target as Node)) return;
  const target = e.target as HTMLElement;
  if (target.closest('[data-radix-popper-content-wrapper]') || target.closest('[role="listbox"]')) {
    return;
  }
  handleCancel();
}

Store Invalidation

❌ Don't Invalidate All Stores for a DataSource

// ❌ Wrong - invalidates ALL stores (list AND detail)
invalidateStoresOnSuccess: ['WKProjects']

// ✅ Correct - only invalidate specific store
invalidateStoresOnSuccess: [{ datasourceId: 'WKProjects', alias: 'wk-projects-all' }]

Next Steps

Previous
Code Organization