How to use Liveblocks Presence with React

In this guide, we’ll be learning how to use Liveblocks Presence with React using the hooks from the @liveblocks/react package.

This guide uses TypeScript. Liveblocks can definitely be used without TypeScript. We believe typings are helpful to make collaborative apps more robust, but if you’d prefer to skip the TypeScript syntax, feel free to write your code in JavaScript.

Get other users in the room

Now that the provider is set up, we can start using the Liveblocks hooks. The first we’ll add is useOthers, a hook that provides us information about which other users are connected to the room.

We can re-export this from liveblocks.config.ts, exactly like we did for RoomProvider.

liveblocks.config.ts
import { createClient } from "@liveblocks/client";import { createRoomContext } from "@liveblocks/react";
const client = createClient({ publicApiKey: "",});
export const { RoomProvider, useOthers, // 👈} = createRoomContext(client);

To show how many other users are in the room, import useOthers into a component and use it as below.

import { RoomProvider, useOthers } from "./liveblocks.config";
function App() { const others = useOthers();
return <div>There are {others.length} other users with you in the room.</div>;}
function Index() { return ( <RoomProvider id="my-room-id"> <App /> </RoomProvider> );}

Great! We’re connected, and we already have information about the other users currently online.

Define initial presence

Most collaborative features rely on each user having their own temporary state, which is then shared with others. For example, in an app using multiplayer cursors, the location of each user’s cursor will be their state. In Liveblocks, we call this presence.

We can use presence to hold any object that we wish to share with others. An example would be the pixel coordinates of a user’s cursor:

cursor: { x: 256, y: 367 }

To start using presence, let’s define a type named Presence in liveblocks.config.ts and use it as a generic argument of createRoomContext. All of our presence hooks returned by createRoomContext will be typed correspondingly to the newly defined Presence type.

liveblocks.config.ts
import { createClient } from "@liveblocks/client";import { createRoomContext } from "@liveblocks/react";
const client = createClient({ publicApiKey: "",});
type Presence = { cursor: { x: number; y: number } | null;};
export const { RoomProvider, useOthers } = createRoomContext<Presence>(client);

Then, define an initialPresence value on our RoomProvider. We’ll set the initial cursor to null to represent a user whose cursor is currently off-screen.

index.ts
import { RoomProvider, useOthers } from "./liveblocks.config";
// App
function Index() { return ( <RoomProvider id="my-room-id" initialPresence={{ cursor: null }}> <App /> </RoomProvider> );}

Update user presence

We can add the useUpdateMyPresence hook to share this information in realtime, and in this case, update the current user cursor position when onPointerMove is called.

First, re-export useUpdateMyPresence like we did with useOthers.

liveblocks.config.ts
// ...
export const { RoomProvider, useOthers, useUpdateMyPresence, // 👈} = createRoomContext<Presence>(client);

To keep this guide concise, we’ll assume that you now understand how to re-export your hooks for every new hook.

Next, import updateMyPresence and call it with the updated cursor coordinates whenever a pointer move event is detected.

import { useUpdateMyPresence } from "./liveblocks.config";
function App() { const updateMyPresence = useUpdateMyPresence();
return ( <div style={{ width: "100vw", height: "100vh" }} onPointerMove={(e) => updateMyPresence({ cursor: { x: e.clientX, y: e.clientY } }) } onPointerLeave={() => updateMyPresence({ cursor: null })} /> );}

We’re setting cursor to null when the user’s pointer leaves the element.

Get other users’ presence

To retrieve each user’s presence, and cursor locations, we can once again add useOthers. This time we’ll use a selector function to map through each user’s presence, and grab their cursor property. If a cursor is set to null, a user is off-screen, so we’ll skip rendering it.

import {  useOthers,  useUpdateMyPresence,  RoomProvider,} from "./liveblocks.config";
function App() { const others = useOthers(); const updateMyPresence = useUpdateMyPresence();
return ( <div style={{ width: "100vw", height: "100vh" }} onPointerMove={(e) => updateMyPresence({ cursor: { x: e.clientX, y: e.clientY } }) } onPointerLeave={() => updateMyPresence({ cursor: null })} > {others.map(({ connectionId, presence }) => presence.cursor ? ( <Cursor key={connectionId} x={presence.cursor.x} y={presence.cursor.y} /> ) : null )} </div> );}
// Basic cursor componentfunction Cursor({ x, y }) { return ( <img style={{ position: "absolute", transform: `translate(${x}px, ${y}px)`, }} src="/assets/cursor.svg" /> );}

Presence isn’t only for multiplayer cursors, and can be helpful for a number of other use cases such as live avatar stacks and realtime form presence.