SyncZone
Hierarchical key prefixing and host control for organized state namespacing.
SyncZone provides hierarchical key namespacing and host management. Zones accumulate key prefixes so child components don't need to repeat the full key path — and nested zones combine their prefixes automatically.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | (string | null)[] | (required) | Key prefix for this zone |
hostRules | 'first' | 'explicit' | 'first' | How to determine the host |
host | string | null | undefined | Explicit host client ID (when hostRules='explicit') |
children | React.ReactNode | (required) | Children |
Basic usage
import { SyncZone, SyncBox } from '@robojs/sync'
function Game({ roomId }: { roomId: string }) {
return (
<SyncZone id={['game', roomId]}>
<SyncBox id={['player']} initialState={{ x: 0, y: 0 }}>
{(state) => <div>Position: {state?.x}, {state?.y}</div>}
</SyncBox>
{/* SyncBox key becomes 'game.[roomId].player' */}
</SyncZone>
)
}import { SyncZone, SyncBox } from '@robojs/sync'
function Game({ roomId }) {
return (
<SyncZone id={['game', roomId]}>
<SyncBox id={['player']} initialState={{ x: 0, y: 0 }}>
{(state) => <div>Position: {state?.x}, {state?.y}</div>}
</SyncBox>
{/* SyncBox key becomes 'game.[roomId].player' */}
</SyncZone>
)
}Nested zones
Prefixes accumulate. Each nested SyncZone appends its id to the parent's prefix, so you can organize state into logical groups without manually building key paths.
<SyncZone id={['game']}>
<SyncZone id={['board']}>
<SyncBox id={['piece']} initialState={{ x: 0 }} />
{/* key: 'game.board.piece' */}
</SyncZone>
<SyncZone id={['chat']}>
<SyncBox id={['messages']} initialState={[]} />
{/* key: 'game.chat.messages' */}
</SyncZone>
</SyncZone><SyncZone id={['game']}>
<SyncZone id={['board']}>
<SyncBox id={['piece']} initialState={{ x: 0 }} />
{/* key: 'game.board.piece' */}
</SyncZone>
<SyncZone id={['chat']}>
<SyncBox id={['messages']} initialState={[]} />
{/* key: 'game.chat.messages' */}
</SyncZone>
</SyncZone>Host rules
First subscriber (default)
The first client to subscribe to the zone key becomes host. If that client disconnects, the next one takes over. This is server-managed.
<SyncZone id={['match']} hostRules="first">
<GameBoard />
</SyncZone><SyncZone id={['match']} hostRules="first">
<GameBoard />
</SyncZone>Explicit host
Set the host manually. Useful for admin-controlled scenarios or server-assigned roles.
<SyncZone id={['match']} hostRules="explicit" host={adminClientId}>
<GameBoard />
</SyncZone><SyncZone id={['match']} hostRules="explicit" host={adminClientId}>
<GameBoard />
</SyncZone>useZoneContext
Access the current zone's context from any child component.
import { useZoneContext } from '@robojs/sync'
function GameStatus() {
const zone = useZoneContext()
if (!zone) return null
return (
<div>
<p>{zone.isHost ? 'You are the host' : `Host: ${zone.hostId}`}</p>
<p>Players: {zone.clients.length}</p>
<p>Status: {zone.connectionStatus}</p>
</div>
)
}import { useZoneContext } from '@robojs/sync'
function GameStatus() {
const zone = useZoneContext()
if (!zone) return null
return (
<div>
<p>{zone.isHost ? 'You are the host' : `Host: ${zone.hostId}`}</p>
<p>Players: {zone.clients.length}</p>
<p>Status: {zone.connectionStatus}</p>
</div>
)
}ZoneContextValue
| Field | Type | Description |
|---|---|---|
prefix | (string | null)[] | Accumulated key prefix |
hostId | string | Host client ID |
isHost | boolean | Whether current client is host |
connectionStatus | ConnectionStatus | 'connecting' | 'connected' | 'disconnected' | 'error' |
clients | Client[] | Clients in this zone |
broadcast | (payload: unknown) => void | Send to all in zone |
send | (clientId: string, payload: unknown) => void | Send to specific client |
useZoneKey
Compute the full key by combining the zone prefix with a local id. Useful when you need to pass the full key to hooks directly rather than relying on SyncBox.
import { useZoneKey, useSyncState } from '@robojs/sync'
function PlayerMarker({ playerId }: { playerId: string }) {
const fullKey = useZoneKey(['player', playerId])
// In <SyncZone id={['game']}>, fullKey = ['game', 'player', playerId]
const [position, setPosition] = useSyncState({ x: 0, y: 0 }, fullKey)
return <div style={{ left: position.x, top: position.y }}>Player</div>
}import { useZoneKey, useSyncState } from '@robojs/sync'
function PlayerMarker({ playerId }) {
const fullKey = useZoneKey(['player', playerId])
// In <SyncZone id={['game']}>, fullKey = ['game', 'player', playerId]
const [position, setPosition] = useSyncState({ x: 0, y: 0 }, fullKey)
return <div style={{ left: position.x, top: position.y }}>Player</div>
}