Live Queries

      +

      Description — Couchbase Lite JavaScript — Reactive Live Queries
      Related Content — SQL++ for Mobile | Query Resultsets | Indexing

      Overview

      Live queries provide real-time updates when the results of a query change. This makes them perfect for building reactive user interfaces that automatically update when data changes.

      When you create a live query, Couchbase Lite monitors the database and notifies you whenever documents that match the query are added, modified, or deleted.

      Creating Live Queries

      Create a live query by adding a change listener to your query:

      Example 1. Basic live query
      const query = database.createQuery(`
          SELECT title FROM tasks
          WHERE completed = false
          ORDER BY createdAt DESC
      `);
      
      // Add change listener - receives array of results
      const token = query.addChangeListener((results) => {
          console.log(`Query results updated: ${results.length} tasks`);
      
          // Update UI with new results
          results.forEach(row => {
              console.log(`Task: ${row.title}`);
          });
      
          // Update your application UI
          updateTaskList(results);
      });
      
      // The listener is now active and will be called whenever results change

      Removing Listeners

      Always remove listeners when they’re no longer needed:

      Example 2. Remove listener
      const query = database.createQuery('SELECT * FROM tasks WHERE completed = false');
      const token = query.addChangeListener(_ => {
          console.log('Results updated');
      });
      
      // Later, remove the listener
      token.remove();
      console.log('Listener removed');

      Multiple Listeners

      You can add multiple listeners to the same query:

      Example 3. Multiple listeners
      const query = database.createQuery('SELECT * FROM tasks WHERE priority >= 3');
      
      // Listener 1: Update UI
      const uiToken = query.addChangeListener(results => {
          updateTaskList(results);
      });
      
      // Listener 2: Log changes
      const logToken = query.addChangeListener(results => {
          console.log(`High priority tasks: ${results.length}`);
      });
      
      // Listener 3: Send analytics
      const analyticsToken = query.addChangeListener(results => {
          trackMetric('high_priority_tasks', results.length);
      });
      
      // Each listener receives independent notifications
      // Remove listeners individually when done
      uiToken.remove();
      logToken.remove();
      analyticsToken.remove();

      Each listener receives independent notifications.

      TypeScript Support

      Type your live query results for type safety:

      Example 4. Typed live queries
      interface TaskResult {
          title: string;
          completed: boolean;
          priority: number;
          createdAt: string;
      }
      
      const query = database.createQuery(`
          SELECT title, completed, priority, createdAt
          FROM tasks
          WHERE completed = false
      `);
      
      // Add typed listener
      const token = query.addChangeListener<TaskResult>((results) => {
          results.forEach(t => {
              const task = t as TaskResult;
              const title: string = task.title;  // Type-safe
              const priority: number = task.priority;
      
              console.log(`${title} - Priority: ${priority}`);
          });
      });

      Error Handling

      Handle errors in live query listeners:

      Example 5. Error handling
      const query = database.createQuery('SELECT * FROM tasks');
      
      query.addChangeListener(results => {
          try {
              // Process results
              updateUI(results);
          } catch (error) {
              console.error('Error updating UI:', error);
              // Handle error without crashing the listener
          }
      });
      
      // Wrap listener registration in try-catch for setup errors
      try {
          query.addChangeListener(results => {
              processResults(results);
          });
      } catch (error) {
          console.error('Failed to create live query listener:', error);
      }

      Live Query Lifecycle

      Example 6. Lifecycle stages
      class TaskManager {
          private query: Query | null = null;
          private database: Database | null = null;
          private listenerToken: ListenerToken | null = null;
      
          async start() {
              this.database = await Database.open({name: "mydb", version: 1, collections: { tasks: {} }});
      
              // Create query
              this.query = this.database.createQuery(`
                  SELECT * FROM tasks
                  WHERE assignedTo = $userId
                  AND completed = false
              `);
      
              // Set parameters
              this.query.parameters = { userId: 'current-user' };
      
              // Start listening
              this.listenerToken = this.query.addChangeListener(results => {
                  this.onResultsChanged(results);
              });
      
              console.log('Live query started');
          }
      
          onResultsChanged(results: QueryAliases[]) {
              console.log(`Tasks updated: ${results.length}`);
              this.updateUI(results);
          }
      
          updateParameters(userId: string) {
              // Changing parameters triggers listener with new results
              if (this.query) {
                  this.query.parameters = { userId };
              }
          }
      
          stop() {
              // Clean up listener
              if (this.listenerToken) {
                  this.listenerToken.remove();
                  this.listenerToken = null;
                  console.log('Live query stopped');
              }
          }
      
          private updateUI(_: QueryAliases[]) {
              // Update application UI
          }
      }
      
      // Usage
      const manager = new TaskManager();
      manager.start();  // Start listening
      
      // Later: change parameters (triggers update)
      manager.updateParameters('different-user');
      
      // Clean up when component unmounts
      manager.stop();

      Limitations

      Database Changes Only:

      • Live queries only respond to local database changes

      • They don’t poll external data sources

      • Replication changes trigger live query updates

      Query Constraints:

      • All SQL++ query limitations apply (see query-n1ql-mobile.adoc#limitations)

      • Complex queries may have performance implications

      • Index availability affects live query performance

      Browser Specific:

      • Live queries depend on browser staying active

      • Background tabs may throttle updates

      • Consider using Service Workers for background updates

      How to . . .

      .

      Dive Deeper . . .

      Mobile Forum | Blog | Tutorials

      .