Presence

Presence is a per-connection state that allows users to view what others are doing within the same room. Presence can be used to represent things such as movement and selections and can be really valuable for users to understand an experience to be multiplayer.

Set presence

To get started with presence for pluv.io, first set a presence config on your createClient config and create your react bundle.

import { createClient, infer } from "@pluv/client";
import { createBundle } from "@pluv/react";
import { z } from "zod";
import type { ioServer } from "./backend/io";

const types = infer((i) => ({ io: i<typeof iServer> }));
const client = createClient({
    types,
    presence: z.object({
        selectionId: z.string().nullable(),
    }),
    // ...
});

export const {
    PluvRoomProvider,
    useMyPresence,
    useMyself,
    useOther,
    useOthers,
} = createBundle(client);

Set initialPresence on PluvRoomProvider

import type { FC } from "react";
import { pluv } from "./frontend/io";

const Room: FC = () => {
    return (
        <PluvRoomProvider
            // Specify the initial presence for each newly connected user
            initialPresence={{
                selectionId: null,
            }}
            room="my-example-room"
        >
            <ChatRoom />
        </PluvRoomProvider>
    );
};

Observing presence

The following example usages of react hooks pertain to users' presence in rooms.

Note: The hooks will trigger a re-render of your component if the values returned change. So consider using the selectors within these hooks to return data that changes as little as you need.

Current user's presence

import { useCallback } from "react";
import { useMyPresence, useMyself } from "./frontend/io";

const [myPresence, updateMyPresence] = useMyPresence();
//     ^? const myPresence: { selectionId: string | null } | null

// It is recommended that you specify a selector in `useMyPresence` to
// minimize re-renders for your component in case your presence state
// contains multiple fields
const [mySelectionId] = useMyPresence((presence) => presence.selectionId);
//     ^? const mySelectionId: string | null

const myself = useMyself();
//    ^? const myself: {
//        connectionId: string;
//        presence: { selectionId: string | null };
//        user: null;
//    } | null;

const mySelectionId = useMyself(({ presence }) => presence.selectionId);
//    ^? const mySelectionId: string | null

// Updating the current user's presence
const selectInput = useCallback((selectionId: string) => {
    updateMyPresence(selectionId);
}, [updateMyPresence]);

Others' presence

import { useOther, useOthers } from "./frontend/io";

const others = useOthers();
//    ^? const others: readonly {
//        connectionId: string;
//        presence: { selectionId: string | null };
//        user: null;
//    }[] | null;

// Recommended that you actually map connection ids, and use useOther
// in another component
const others = useOthers((others) => {
//    ^? const others: readonly string[] | null;
    return others.map((other) => other.connectionId);
});

const other = useOther(connectionId);
//    ^? const other: {
//        connectionId: string;
//        presence: { selectionId: string | null };
//        user: null;
//    } | null;

// Example with selector
const other = useOther(connectionId, (other) => {
    return other.presence.selectionId;
});