Live Queries
Live queries automatically update when the underlying data changes, providing real-time reactive data for your applications.
Overview
Live queries in Routier allow you to subscribe to data changes and automatically receive updates when the underlying data is modified. This is perfect for building reactive UIs and real-time applications.
Quick Navigation
- Quick Reference
- Important: Callbacks vs Async
- Basic Live Queries
- Advanced Live Query Patterns
- Managing Live Queries
- Performance Considerations
- Common Patterns
- Best Practices
- Error Handling
- Related Topics
Quick Reference
| Method | Description | Example |
|---|---|---|
subscribe() | Enable live updates | ctx.products.subscribe().toArray(callback) |
unsubscribe() | Stop live updates | query.unsubscribe() |
Important: Callbacks vs Async
When using .subscribe(), you must use callback-based methods (not async methods):
// ✅ Correct: Use callbacks with .subscribe()
ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
console.log(result.data);
}
});
// ❌ Incorrect: Cannot use async methods with .subscribe()
// This will NOT work:
const data = await ctx.users.subscribe().toArrayAsync();
The reason: subscriptions need to trigger the callback whenever data changes, which can’t be done with promises. Callbacks can be invoked at any time, making them perfect for reactive updates.
Basic Live Queries
Simple Live Query
// Create a live query that updates automatically
ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
console.log(result.data); // Live data updates automatically
}
});
// The query will automatically update when users are added, updated, or removed
Live Query with Filtering
// Live query with filtering - updates when filtered data changes
ctx.users
.where((u) => u.isActive === true)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Active users:", result.data);
}
});
// This will automatically update when:
// - New active users are added
// - Existing users become active/inactive
// - Active users are removed
Live Query with Sorting
// Live query with sorting - maintains sort order as data changes
ctx.users
.sort((u) => u.name)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Sorted users:", result.data);
}
});
// This will automatically update and maintain alphabetical order when:
// - New users are added
// - User names are updated
// - Users are removed
Advanced Live Query Patterns
Live Aggregation
// Live count that updates automatically
ctx.users.subscribe().count((result) => {
if (result.ok === "success") {
console.log("User count:", result.data);
}
});
// Live sum that updates automatically
ctx.products
.where((p) => p.inStock === true)
.subscribe()
.sum(
(result, selector) => {
if (result.ok === "success") {
console.log("Total value:", result.data);
}
},
(p) => p.price
);
Live Pagination
// Live paginated results
const pageSize = 10;
const currentPage = 0;
ctx.users
.skip(currentPage * pageSize)
.take(pageSize)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Current page:", result.data);
}
});
// This will update when users are added/removed/modified
// affecting the current page
Live Single Item
// Live single item query
ctx.users
.sort((u) => u.createdAt)
.subscribe()
.firstOrUndefined((result) => {
if (result.ok === "success") {
console.log("First user:", result.data);
}
});
// This will update when the first user changes
Managing Live Queries
Unsubscribing
// Create a live query
const subscription = ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
console.log(result.data);
}
});
// Later, unsubscribe to stop updates
subscription.unsubscribe();
Conditional Live Queries
// Only create live query if needed
let unsubscribe: (() => void) | null = null;
if (shouldUseLiveQuery) {
unsubscribe = ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
// Handle data
}
}).unsubscribe;
} else {
ctx.users.toArrayAsync().then((data) => {
// Handle data
});
}
Performance Considerations
Efficient Live Queries
// Good: Apply filters before subscribing
ctx.products
.where((p) => p.price > 100)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
// Handle expensive products
}
});
// Less efficient: Subscribe to all data then filter
ctx.products.subscribe().toArray((result) => {
if (result.ok === "success") {
const expensiveProducts = result.data.filter((p) => p.price > 100);
// Handle filtered results
}
});
Memory Management
// Clean up live queries when components unmount
class UserComponent {
private unsubscribe: (() => void) | null = null;
initialize() {
const subscription = ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
// Handle data
}
});
this.unsubscribe = subscription.unsubscribe;
}
destroy() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
}
Common Patterns
Real-time Dashboard
// Live dashboard with multiple live queries
ctx.users.subscribe().count((result) => {
if (result.ok === "success") {
console.log("Total users:", result.data);
}
});
ctx.products
.where((p) => p.inStock === true)
.subscribe()
.count((result) => {
if (result.ok === "success") {
console.log("Active products:", result.data);
}
});
ctx.products
.orderByDescending((p) => p.sales)
.take(5)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Top sellers:", result.data);
}
});
Live Search Results
// Live search that updates as user types
const searchTerm = "john";
ctx.users
.where((u) => u.name.toLowerCase().includes(searchTerm.toLowerCase()))
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Search results:", result.data);
}
});
Live Notifications
// Live query for unread notifications
ctx.notifications
.where((n) => n.isRead === false)
.orderByDescending((n) => n.createdAt)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
console.log("Unread notifications:", result.data);
}
});
Best Practices
1. Use Live Queries for Real-time Data
// Good: Use live queries for data that changes frequently
ctx.messages.subscribe().toArray((result) => {
if (result.ok === "success") {
console.log("Messages:", result.data);
}
});
// Less useful: Static data doesn't need live queries
const staticConfig = await ctx.config.toArrayAsync();
2. Apply Filters Before Subscribing
// Good: Filter before subscribing to reduce tracked changes
ctx.users
.where((u) => u.isActive === true)
.subscribe()
.toArray((result) => {
if (result.ok === "success") {
// Handle active users
}
});
// Less efficient: Subscribe to all data, then filter in component
ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
const activeUsers = result.data.filter((u) => u.isActive);
// Handle filtered results
}
});
3. Clean Up Subscriptions
// Always unsubscribe when done
const subscription = ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
// Handle data
}
});
// Clean up
subscription.unsubscribe();
4. Use Appropriate Terminal Methods
// Use count() for counts
ctx.users.subscribe().count((result) => {
if (result.ok === "success") {
console.log("Count:", result.data);
}
});
// Use toArray() for lists
ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
console.log("List:", result.data);
}
});
// Use firstOrUndefined() for single items
ctx.users.subscribe().firstOrUndefined((result) => {
if (result.ok === "success") {
console.log("First:", result.data);
}
});
Error Handling
Live Query Error Handling
ctx.users.subscribe().toArray((result) => {
if (result.ok === "success") {
// Handle live updates
} else {
console.error("Live query error:", result.error);
// Fallback to regular query
ctx.users.toArrayAsync().then((users) => {
// Handle fallback data
});
}
});
Related Topics
- Queries - Basic query operations
- State Management - Managing application state
- Data Manipulation - Modifying data