Skip to main content

πŸ“¦ useSupastashData

The useSupastashData hook is the core way to access and sync data in Supastash. It handles offline-first fetching, real-time updates, manual refreshes, and fine-grained event control β€” all built on SQLite + Supabase.

This doc is your all-in-one guide to understanding how to use it properly.


🧠 What It Does​

useSupastashData is a React hook that:

  • Loads data instantly from your local SQLite table (even without internet).
  • Keeps that data in sync with Supabase β€” both ways.
  • Subscribes to Supabase realtime changes (INSERT, UPDATE, DELETE).
  • Minimizes re-renders via smart batching + memoization.
  • Supports filtering, grouping, lazy fetch, and custom callbacks.

It's made to be plug-and-play β€” but also powerful enough for edge cases.


βœ… Key Features​

  • πŸ”„ Offline-first loading from local database
  • πŸ”Œ Realtime Supabase sync (insert/update/delete)
  • 🧠 Memoized output (no unnecessary renders)
  • ⏯️ Lazy mode for manual control
  • πŸ“€ Custom callbacks for push/pull sync events
  • πŸ“¦ Batched updates via flushIntervalMs
  • πŸ” Filter-aware both locally and remotely
  • 🧩 Grouping support with extraMapKeys

πŸ§ͺ Basic Usage​

const { data, dataMap } = useSupastashData<Order>("orders");

This loads orders from your local SQLite database, keeps it synced, and returns two things:

  • data – An array of all rows
  • dataMap – A map keyed by id

πŸ” With Filters, Lazy Load, and Callbacks​

const { data, dataMap, trigger, cancel, groupedBy } = useSupastashData<Order>(
"orders",
{
shouldFetch: !!userId, // Only fetch if user is available
lazy: true, // Wait for manual trigger
flushIntervalMs: 200, // Reduce render frequency
filter: {
column: "user_id",
operator: "eq",
value: userId,
},
extraMapKeys: ["status", "user_id"],
onInsert: (order) => console.log("Inserted:", order),
onUpdate: (order) => console.log("Updated:", order),
onDelete: (order) => console.log("Deleted:", order),
}
);

useEffect(() => {
trigger(); // Only needed if lazy: true
}, []);

πŸ“¦ Return Values​

NameTypeDescription
dataR[]Array of rows from local table
dataMapMap<string, R>Keyed map of rows by their id
groupedByRecord<string, Map<string, R[]>>Optional grouped maps by field (if specified)
trigger() => voidStarts fetch + subscription (if lazy)
cancel() => voidStops sync and fetch (useful for cleanup)

βš™οΈ Hook Options​

OptionTypeDefaultDescription
shouldFetchbooleantrueFetch on mount?
lazybooleanfalseIf true, nothing runs until you call trigger()
extraMapKeysstring[]β€”Build grouped maps (e.g., groupedBy.chat_id)
filterRealtimeFilterβ€”Only sync matching rows (eq, lt, etc.)
flushIntervalMsnumber100Debounce for batched UI updates
realtimebooleantrueSubscribe to Supabase postgres_changes
limitnumber200Limit for local rows
daylengthnumberβ€”Fetch only records from the last n days
useFilterWhileSyncingbooleantrueUse same filter when syncing with Supabase
onInsert(item: any) => voidβ€”Called on Supabase INSERT
onUpdate(item: any) => voidβ€”Called on Supabase UPDATE
onDelete(item: any) => voidβ€”Called on Supabase DELETE
onInsertAndUpdate(item: any) => voidβ€”Shortcut for insert + update
onPushToRemote(items: any[]) => Promise<boolean>β€”Handle custom push logic (must return success boolean)

extraMapKeys – Fast Lookups & Grouping Built-In​

When working with local data like orders, it’s common to filter or group by fields like user_id or status. Doing that with .filter() every time isn’t just repetitive β€” it’s inefficient, especially as your data grows.

That’s where extraMapKeys in useSupatashData comes in.

What It Does​

Pass one or more column names to extraMapKeys, and Supastash will automatically generate lookup maps for them:

const { data, dataMap, groupedBy } = useSupatashData("orders", {
extraMapKeys: ["user_id", "status"],
});

Now you get:

  • dataMap.get("order_123") – fast ID lookup
  • groupedBy.user_id.get("user_42") – all orders for a user
  • groupedBy.status.get("pending") – all pending orders

No extra filtering, no extra logic.

Why It Matters​

It’s like having indexed views of your local data β€” ready to use, already synced, and fast. This is especially useful for rendering dashboards, grouped lists, or filtering data by key fields.

Example​

const userOrders = groupedBy.user_id.get(user.id) ?? [];

One line. No filter(). Faster and scalable.

Use it when you know you’ll be accessing your data by specific fields often. It keeps your code cleaner and your UI snappier.


πŸ” Advanced Callbacks​

onInsertAndUpdate​

This fires on both inserts and updates from Supabase. Perfect for logic like marking messages as "received":

onInsertAndUpdate: async (payload) => {
const { data: local } = await supastash
.from("messages")
.select("*")
.eq("id", payload.id)
.run();

if (!local || local.is_received) return;

await supastash
.from("messages")
.upsert({ ...local, is_received: true })
.run();
};

onPushToRemote​

Customize how local records get pushed to Supabase:

onPushToRemote: async (payload) => {
const result = await supabase.from("messages").upsert(payload);
return !result.error;
};

Return true on success β€” Supastash will retry otherwise.


πŸ”„ Manual Refresh​

Need to manually trigger a refresh? Use these from:

import {
refreshTable,
refreshAllTables,
} from "supastash/utils/sync/refreshTables";

πŸ” refreshTable(table: string): void​

For refreshing one table's local data:

refreshTable("orders");

Emits a refresh:orders event. Re-fetches local rows + triggers UI update.

πŸ” refreshAllTables(): void​

Refreshes everything:

refreshAllTables();

Good after a full sync or reset.

⚠️ refreshTableWithPayload() (Deprecated)​

This used to manually reflect data in the UI. No longer needed β€” use Supastash queries or refreshTable().

// Deprecated β€” avoid
refreshTableWithPayload("orders", { id: "abc", ... }, "update");

πŸ” Behind the Scenes​

  • πŸ” Supastash uses a version-based cache: if nothing changes, no re-renders.
  • 🧼 Realtime listeners are de-duped per table + filter.
  • ⚑ Flushes UI updates only after debounce (default 100ms).
  • 🧠 Grouped maps (via extraMapKeys) use Map<string, R[]>.

πŸ’‘ Best Practices​

  • Use dataMap for instant lookups (e.g., dataMap.get(id))
  • Use groupedBy.chat_id for message grouping
  • Use lazy: true in modal screens or deeply nested routes
  • Bump flushIntervalMs higher if syncing high-volume tables
  • Leverage onPushToRemote for custom API pipelines

πŸ”— What’s Next​


This hook is the heart of Supastash.