Best Practices

Performance Best Practices

Optimize your application performance with these tips.

Store Configuration

Use select for Lookup Stores

Only fetch fields you need:

// ✅ Good - only ID and display name
const store = useStore<Customer>({
  datasourceId: 'Customers',
  alias: 'customer-options',
  select: ['customerId', 'customerName'],
  limit: 1000,
});

// ❌ Bad - fetches all fields
const store = useStore<Customer>({
  datasourceId: 'Customers',
  alias: 'customer-options',
  limit: 1000,
});

Use filterLocally for Small Static Datasets

When you've loaded all data:

const store = useStore<Status>({
  datasourceId: 'StatusLookup',
  alias: 'status-combobox',
  limit: 100,  // Load all statuses
  autoQuery: true,
  filterLocally: true,  // Filter client-side, no DB round-trips
});

Appropriate limit Values

  • List pages: 20 (standard pagination)
  • Lookup stores: 1000 (for all options)
  • Detail pages: 1 (single record)
  • Export operations: 5000+ (if needed)

Query Optimization

Avoid Unnecessary Queries

The store handles query deduplication automatically:

// ✅ Good - store handles deduplication
useEffect(() => {
  store.executeQuery({ query: { match: { projectId } } });
}, [projectId, store]);

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

Consolidate Multiple useEffects

// ❌ Bad - duplicate queries on mount
useEffect(() => {
  if (customerId) store.setSmartSearchFilters([...]);
  store.executeQuery();
}, [customerId, store]);

useEffect(() => {
  store.executeQuery();  // Also runs on mount!
}, [includeArchived, store]);

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

Component Optimization

Use useMemo for Column Definitions

export default function useTableColumns(store: Store<Entity>) {
  return useMemo(
    () => [
      // column definitions
    ],
    [store],
  );
}

Use useMemo for Options

export default function useSmartSearchColumns() {
  const { rows: customerOptions } = useCustomerOptions();
  
  return useMemo(
    () => [
      {
        key: 'customerId',
        options: customerOptions,
        // ...
      },
    ],
    [customerOptions],
  );
}

Store Sharing

Share Stores Across Components

Stores with the same key are automatically shared:

// ✅ Good - both use the same store instance
const tableStore = useEntityStore();
const formStore = useEntityStore();  // Same instance!

// ❌ Bad - creates separate stores
const tableStore = useStore({ alias: 'entity-list', ... });
const formStore = useStore({ alias: 'entity-form', ... });  // Different!

Auto-Refresh

Use autoRefresh sparingly - only for data that needs real-time updates:

// ✅ Good - notifications need real-time updates
const store = useStore<Notifications>({
  datasourceId: 'Notifications',
  alias: 'notifications-list',
  autoRefresh: true,  // Subscribe to SSE
});

// ❌ Bad - static lookup data doesn't need auto-refresh
const store = useStore<Status>({
  datasourceId: 'StatusLookup',
  alias: 'status-combobox',
  autoRefresh: true,  // Unnecessary
});

Next Steps

Previous
Overview