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 clientID
s of the clients
that are present. To do something useful with them, you will combine these
clientID
s 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.