LogoRobo.js

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

PropTypeDefaultDescription
id(string | null)[](required)Key prefix for this zone
hostRules'first' | 'explicit''first'How to determine the host
hoststring | nullundefinedExplicit host client ID (when hostRules='explicit')
childrenReact.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

FieldTypeDescription
prefix(string | null)[]Accumulated key prefix
hostIdstringHost client ID
isHostbooleanWhether current client is host
connectionStatusConnectionStatus'connecting' | 'connected' | 'disconnected' | 'error'
clientsClient[]Clients in this zone
broadcast(payload: unknown) => voidSend to all in zone
send(clientId: string, payload: unknown) => voidSend 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>
}

Next steps

On this page