Skip to main content
Subscriptions are the core of Lark’s real-time capabilities. When data changes on the server, your callback fires with the updated data — no polling, no manual refreshes. For a conceptual overview of how subscriptions work under the hood (delta sync, shared views), see Subscriptions.

Basic usage

Call ref.on(eventType, callback) to start listening. It returns an unsubscribe function you call when you’re done:
import { LarkDatabase } from "@lark-sh/client";

const db = new LarkDatabase("my-project/my-database");
await db.connect({ anonymous: true });

const unsubscribe = db.ref("scores/player1").on("value", (snapshot) => {
  console.log("Current score:", snapshot.val());
});

// Later, stop listening
unsubscribe();
The unsubscribe pattern works perfectly with React’s useEffect cleanup, Vue’s onUnmounted, and similar framework lifecycle hooks.

Event types

value

Fires once with the initial data, then again every time anything at the location (or its children) changes. The callback receives the complete snapshot.
const unsubscribe = db.ref("game/state").on("value", (snapshot) => {
  const state = snapshot.val();
  console.log("Full game state:", state);
});

child_added

Fires once for each existing child, then again whenever a new child is added. The callback receives the child’s snapshot and the key of the previous child (for ordering).
const unsubscribe = db.ref("chat/messages").on(
  "child_added",
  (snapshot, prevChildKey) => {
    console.log("New message:", snapshot.key, snapshot.val());
    console.log("After:", prevChildKey);
  }
);

child_changed

Fires when an existing child’s value changes. Does not fire for additions or removals.
const unsubscribe = db.ref("players").on(
  "child_changed",
  (snapshot, prevChildKey) => {
    console.log("Player updated:", snapshot.key, snapshot.val());
  }
);

child_removed

Fires when a child is removed.
const unsubscribe = db.ref("players").on("child_removed", (snapshot) => {
  console.log("Player left:", snapshot.key);
});

child_moved

Fires when a child’s sort order changes (due to priority or value changes when using ordered queries).
const unsubscribe = db.ref("leaderboard")
  .orderByChild("score")
  .on("child_moved", (snapshot, prevChildKey) => {
    console.log("Rank changed:", snapshot.key, "now after:", prevChildKey);
  });

Practical examples

Player list

Keep a live roster of players in a multiplayer game:
const players = new Map<string, any>();

const unsubAdded = db.ref("game/players").on("child_added", (snapshot) => {
  players.set(snapshot.key, snapshot.val());
  console.log(`${snapshot.val().name} joined. Total: ${players.size}`);
});

const unsubRemoved = db.ref("game/players").on("child_removed", (snapshot) => {
  players.delete(snapshot.key);
  console.log(`${snapshot.val().name} left. Total: ${players.size}`);
});

const unsubChanged = db.ref("game/players").on("child_changed", (snapshot) => {
  players.set(snapshot.key, snapshot.val());
  console.log(`${snapshot.val().name} updated.`);
});

// Clean up all listeners
function cleanup() {
  unsubAdded();
  unsubRemoved();
  unsubChanged();
}

Chat messages

Listen for messages added to a chat room:
const unsubscribe = db.ref("rooms/lobby/messages")
  .orderByChild("timestamp")
  .limitToLast(50)
  .on("child_added", (snapshot) => {
    const msg = snapshot.val();
    displayMessage(msg.sender, msg.text, msg.timestamp);
  });

Live game score

Track a single value that changes frequently:
const unsubscribe = db.ref("game/score").on("value", (snapshot) => {
  const score = snapshot.val() ?? 0;
  updateScoreDisplay(score);
});

Leaderboard

Watch a sorted, limited query:
const unsubscribe = db.ref("players")
  .orderByChild("score")
  .limitToLast(10)
  .on("value", (snapshot) => {
    const leaderboard: Array<{ name: string; score: number }> = [];
    snapshot.forEach((child) => {
      leaderboard.push(child.val());
    });
    leaderboard.reverse(); // limitToLast returns ascending, flip for top-first
    renderLeaderboard(leaderboard);
  });

Unsubscribing

Always unsubscribe when you no longer need updates. This frees up resources on both the client and server.
const unsubscribe = db.ref("data").on("value", callback);

// Call it when done
unsubscribe();
Forgetting to unsubscribe is a common source of memory leaks and unexpected behavior. Always clean up your listeners when a component unmounts or a view is no longer active.

React example

import { useEffect, useState } from "react";
import { LarkDatabase } from "@lark-sh/client";

function ScoreDisplay({ db, playerId }: { db: LarkDatabase; playerId: string }) {
  const [score, setScore] = useState(0);

  useEffect(() => {
    const unsubscribe = db.ref(`scores/${playerId}`).on("value", (snapshot) => {
      setScore(snapshot.val() ?? 0);
    });

    return unsubscribe;
  }, [db, playerId]);

  return <div>Score: {score}</div>;
}