Core Concepts
Presence

Presence

Reflect keeps track of the clients currently connected to a room. You can use this to show users who else is connected at any point in time, or to implement features like cursors or selection state which should only be visible when the associated client is connected.

Use the subscribeToPresence method of Reflect to watch for changes to the set of clients connected to the room:

r.subscribeToPresence((presentClientIDs) => {
  myElm.textContent = `Active Users: ${presentClientIDs.length}`;
});

When a client is offline, it will only see itself as being present. presentClientIDs will always contain the client's own client ID.

Presence State

The subscribeToPresence method only tells you the clientIDs of the clients that are present. To do something useful with them, you will combine these clientIDs with additional synced data about clients. This additional client data is custom to your application. It is read and written like any other Reflect data and can be modeled using the Rails helper library.

Presence state uses a special key space that the Reflect server knows about. The key space for this starts with -/p/${clientID}/. When a client is known to not be able to connect again the Reflect server will remove all keys in this key space.

For example you might model your client data like so:

import { generatePresence } from "@rocicorp/rails";
 
type Client = {
  clientID: string;
  name: string;
  cursor: { x: number; y: number };
};
 
export const {
  set: setClient,
  get: getClient,
  init: initClient,
  update: updateClient,
  list: listClients,
} = generatePresence<Client>("client");

You can then filter this client data to just render the currently connected clients:

let presentClientIDs: readonly ClientID[] = [];
r.subscribeToPresence((clientIDs) => {
  presentClientIDs = clientIDs;
});
 
let clients: Record<ClientID, Client> = {};
r.subscribe(
  async (tx) => {
    const result: Record<ClientID, Client> = {};
    for (const client of await listClients(tx)) {
      result[client.clientID] = client;
    }
    return result;
  },
  (result) => {
    clients = result;
    render();
  },
);
 
function render() {
  for (const presentClientID of presentClientIDs) {
    const presentClient = clients[presentClientID];
    if (presentClient) {
      renderCursor(presentClient);
    }
  }
}

React

In React you can use the usePresence hook in combination with the useSubscribe hook.

import { usePresence, useSubscribe } from "@rocicorp/reflect/react";
 
const presentClientIDs = usePresence(r);
const presentClients = useSubscribe(r,
  async (tx) => {
    const result = [];
    for (const clientID of presentClientIDs) {
      const presentClient = await getClient(tx, {clientID});
      if (presentClient) {
        result.push(presentClient);
      }
    }
    return result;
  },
  [],
  [presentClientIDs]
);
 
return (
  <div>
    {presentClients.map(presentClient => (
      <Cursor
        key={presentClient.id}
        name={presentClient.name}
        cursor={presentClient.cursor}
        ></Cursor>
    ))}
  </div>
);

Examples

For a full working example of using presence in Reflect, see Draw.