useClientId
useClientId returns this tab’s identity on a bus — the same id that stamps
every state patch and event this tab sends. It answers “which one am I?” so
that “this tab wrote it”, “this tab holds the lock”, and “this presence dot
is me” all agree.
import { usePeers, useClientId } from 'use-everywhere';
function TabStrip() { const me = useClientId(); const peers = usePeers(); // everyone except me return ( <p> me: {me} · also here: {peers.map((p) => `${p.kind} ${p.id}`).join(', ') || 'nobody'} </p> );}Signature
Section titled “Signature”function useClientId(options?: { name?: string }): string;Options
Section titled “Options”| Option | Type | Default | What it does |
|---|---|---|---|
name | string | 'use-everywhere' | Which bus’s id to return. One bus per name, one id per bus per tab. |
Return value
Section titled “Return value”A 16-character hex string from crypto.getRandomValues, stable for the life of
the page. It is:
- the
idother tabs see for you inusePeers, - the
meta.clientIdother tabs receive with your events and state patches, - new on every full page load (it identifies a page instance, not a user or a browser).
On the server it is the empty string. An id invented during server
rendering could never match the one the browser mints, so rendering it into
markup would mismatch on hydration. The hook returns '' for the server render
and for the client’s hydrating render, then the real id arrives in the commit
immediately after. Treat '' as “not known yet”:
const me = useClientId();if (!me) return <Placeholder />; // first paint under SSRWithout SSR — a plain Vite or CRA app — you will never observe ''.
Worked example: claiming a lock you can recognize
Section titled “Worked example: claiming a lock you can recognize”The id’s whole job is being comparable across features. Here a tab claims a lock in shared state, and every tab — including the claimant — can tell whose it is:
const me = useClientId();const [owner, setOwner] = useSharedState<string | null>('export:owner', null);
const claim = () => setOwner(me);const mine = owner === me; // am I the one exporting?const held = owner !== null; // is anyone?See the full pattern in the single-flight recipe.
Gotchas
Section titled “Gotchas”- Match the name. Ids are per bus: comparing
useClientId()(default bus) againstmeta.clientIdfrom a channel named'auth'compares ids from two different buses. Use the samenameeverywhere you want the ids to line up. - Not persistent, not secret. A refresh mints a new id, and any code on your origin can read it. It’s a coordination handle, not authentication.
Where to next
Section titled “Where to next”usePeers— the other side of the roster.- The mental model — why one bus has one id, and why that’s load-bearing.