Overview

useIsSynchronized returns true when your local React Together state is fully synchronized with the shared model, and false when synchronization is in progress or when not connected to a session. This hook is crucial for showing loading states and ensuring users know when their changes are fully propagated.

Perfect for: Loading indicators, sync status displays, preventing actions during sync, optimistic UI patterns, and debugging synchronization issues.

Basic Usage

import { useIsSynchronized } from 'react-together'

function SyncStatus() {
  const isSynchronized = useIsSynchronized()
  
  return (
    <div className="sync-indicator">
      {isSynchronized ? (
        <span className="text-green-600">✓ Synchronized</span>
      ) : (
        <span className="text-yellow-600">⏳ Synchronizing...</span>
      )}
    </div>
  )
}

Signature

useIsSynchronized(): boolean

Return Value

isSynchronized
boolean

true if connected to a session and all state is synchronized with the model, false during sync or when disconnected

How It Works

The hook returns true when all of these conditions are met:

  1. Connected to session - User is in a React Together session
  2. Has participants - At least one user (including yourself) has state
  3. Model synchronized - The underlying Croquet model is caught up with events

Examples

Synchronization Loading Indicator

Show a loading state while state synchronization is in progress:

import { useIsSynchronized, useStateTogether } from 'react-together'

function CollaborativeCounter() {
  const isSynchronized = useIsSynchronized()
  const [count, setCount] = useStateTogether('counter', 0)
  
  return (
    <div className="counter-widget">
      <div className="header">
        <h3>Shared Counter</h3>
        <div className="sync-status">
          {isSynchronized ? (
            <span className="status-badge synchronized">
              ✓ Live
            </span>
          ) : (
            <span className="status-badge synchronizing">
              <span className="spinner"></span>
              Syncing...
            </span>
          )}
        </div>
      </div>
      
      <div className="counter-display">
        <span className={isSynchronized ? 'count' : 'count syncing'}>
          {count}
        </span>
      </div>
      
      <div className="counter-controls">
        <button
          onClick={() => setCount(count - 1)}
          disabled={!isSynchronized}
          className={!isSynchronized ? 'disabled' : ''}
        >
          -
        </button>
        <button
          onClick={() => setCount(count + 1)}
          disabled={!isSynchronized}
          className={!isSynchronized ? 'disabled' : ''}
        >
          +
        </button>
      </div>
      
      {!isSynchronized && (
        <div className="sync-notice">
          Waiting for synchronization...
        </div>
      )}
    </div>
  )
}

Advanced Connection Dashboard

Build a comprehensive status dashboard:

import { 
  useIsSynchronized, 
  useIsTogether, 
  useConnectedUsers,
  useStateTogether
} from 'react-together'
import { useEffect, useState } from 'react'

function ConnectionDashboard() {
  const isTogether = useIsTogether()
  const isSynchronized = useIsSynchronized()
  const connectedUsers = useConnectedUsers()
  const [testData, setTestData] = useStateTogether('test', 'initial')
  
  const [syncHistory, setSyncHistory] = useState<Array<{
    timestamp: Date
    synchronized: boolean
  }>>([])
  
  // Track sync state changes
  useEffect(() => {
    setSyncHistory(prev => [
      ...prev.slice(-9), // Keep last 10 entries
      {
        timestamp: new Date(),
        synchronized: isSynchronized
      }
    ])
  }, [isSynchronized])
  
  const getStatusColor = () => {
    if (!isTogether) return 'gray'
    if (isSynchronized) return 'green'
    return 'yellow'
  }
  
  const getStatusText = () => {
    if (!isTogether) return 'Disconnected'
    if (isSynchronized) return 'Synchronized'
    return 'Synchronizing'
  }
  
  return (
    <div className="connection-dashboard">
      <div className="dashboard-header">
        <h2>Real-time Status</h2>
        <div 
          className={`status-indicator ${getStatusColor()}`}
          title={getStatusText()}
        >
          <div className="status-dot"></div>
          {getStatusText()}
        </div>
      </div>
      
      <div className="metrics-grid">
        <div className="metric">
          <label>Connection</label>
          <span className={isTogether ? 'connected' : 'disconnected'}>
            {isTogether ? 'Active' : 'Inactive'}
          </span>
        </div>
        
        <div className="metric">
          <label>Synchronization</label>
          <span className={isSynchronized ? 'synced' : 'syncing'}>
            {isSynchronized ? 'Complete' : 'In Progress'}
          </span>
        </div>
        
        <div className="metric">
          <label>Connected Users</label>
          <span>{connectedUsers.length}</span>
        </div>
        
        <div className="metric">
          <label>Model State</label>
          <span className={isSynchronized ? 'current' : 'updating'}>
            {isSynchronized ? 'Current' : 'Updating'}
          </span>
        </div>
      </div>
      
      {/* Sync History */}
      <div className="sync-history">
        <h4>Synchronization History</h4>
        <div className="history-timeline">
          {syncHistory.map((entry, index) => (
            <div
              key={index}
              className={`history-entry ${entry.synchronized ? 'synced' : 'syncing'}`}
              title={entry.timestamp.toLocaleTimeString()}
            >
              <div className="entry-dot"></div>
              <span className="entry-time">
                {entry.timestamp.toLocaleTimeString()}
              </span>
              <span className="entry-status">
                {entry.synchronized ? 'Synced' : 'Syncing'}
              </span>
            </div>
          ))}
        </div>
      </div>
      
      {/* Test Data Area */}
      <div className="test-area">
        <h4>Test Synchronization</h4>
        <div className="test-controls">
          <input
            type="text"
            value={testData}
            onChange={(e) => setTestData(e.target.value)}
            placeholder="Type to test sync..."
            disabled={!isSynchronized}
          />
          <button
            onClick={() => setTestData(`Updated at ${new Date().toLocaleTimeString()}`)}
            disabled={!isSynchronized}
          >
            Update Test Data
          </button>
        </div>
        {!isSynchronized && (
          <p className="sync-warning">
            Changes disabled while synchronizing...
          </p>
        )}
      </div>
    </div>
  )
}

Optimistic UI with Sync Feedback

Show immediate feedback while ensuring sync status is clear:

import { useIsSynchronized, useStateTogether } from 'react-together'
import { useState, useEffect } from 'react'

interface TodoItem {
  id: string
  text: string
  completed: boolean
  createdAt: string
}

function OptimisticTodoList() {
  const isSynchronized = useIsSynchronized()
  const [todos, setTodos] = useStateTogether<TodoItem[]>('todos', [])
  const [pendingChanges, setPendingChanges] = useState<Set<string>>(new Set())
  
  // Clear pending changes when synchronized
  useEffect(() => {
    if (isSynchronized) {
      setPendingChanges(new Set())
    }
  }, [isSynchronized])
  
  const addTodo = (text: string) => {
    const newTodo: TodoItem = {
      id: Date.now().toString(),
      text,
      completed: false,
      createdAt: new Date().toISOString()
    }
    
    // Optimistically update
    setTodos([...todos, newTodo])
    
    // Track as pending
    setPendingChanges(prev => new Set([...prev, newTodo.id]))
  }
  
  const toggleTodo = (id: string) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ))
    setPendingChanges(prev => new Set([...prev, id]))
  }
  
  const deleteTodo = (id: string) => {
    setTodos(todos.filter(todo => todo.id !== id))
    setPendingChanges(prev => new Set([...prev, id]))
  }
  
  return (
    <div className="optimistic-todo-list">
      <div className="header">
        <h2>Collaborative Todo List</h2>
        <div className="sync-status">
          {isSynchronized ? (
            <span className="status-synced">
              ✓ All changes saved
            </span>
          ) : (
            <span className="status-syncing">
              <span className="pulse-icon"></span>
              Saving changes...
            </span>
          )}
        </div>
      </div>
      
      <div className="add-todo">
        <input
          type="text"
          placeholder="Add a new todo..."
          onKeyPress={(e) => {
            if (e.key === 'Enter' && e.currentTarget.value.trim()) {
              addTodo(e.currentTarget.value.trim())
              e.currentTarget.value = ''
            }
          }}
        />
      </div>
      
      <div className="todo-list">
        {todos.map(todo => {
          const isPending = pendingChanges.has(todo.id)
          
          return (
            <div
              key={todo.id}
              className={`todo-item ${todo.completed ? 'completed' : ''} ${isPending ? 'pending' : ''}`}
            >
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              <span className="todo-text">{todo.text}</span>
              
              {isPending && !isSynchronized && (
                <span className="pending-indicator" title="Syncing change...">

                </span>
              )}
              
              <button
                onClick={() => deleteTodo(todo.id)}
                className="delete-btn"
                title="Delete todo"
              >
                ×
              </button>
            </div>
          )
        })}
      </div>
      
      {pendingChanges.size > 0 && !isSynchronized && (
        <div className="pending-summary">
          {pendingChanges.size} change(s) pending synchronization...
        </div>
      )}
    </div>
  )
}

Form Submission with Sync Validation

Prevent form submission until changes are synchronized:

import { useIsSynchronized, useStateTogether } from 'react-together'
import { useState } from 'react'

function CollaborativeForm() {
  const isSynchronized = useIsSynchronized()
  const [formData, setFormData] = useStateTogether('form', {
    name: '',
    email: '',
    message: ''
  })
  const [isSubmitting, setIsSubmitting] = useState(false)
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!isSynchronized) {
      alert('Please wait for synchronization to complete before submitting.')
      return
    }
    
    setIsSubmitting(true)
    
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 2000))
      alert('Form submitted successfully!')
      setFormData({ name: '', email: '', message: '' })
    } catch (error) {
      alert('Submission failed. Please try again.')
    } finally {
      setIsSubmitting(false)
    }
  }
  
  const updateField = (field: keyof typeof formData, value: string) => {
    setFormData({ ...formData, [field]: value })
  }
  
  const canSubmit = isSynchronized && !isSubmitting && 
    formData.name && formData.email && formData.message
  
  return (
    <div className="collaborative-form">
      <div className="form-header">
        <h2>Contact Form</h2>
        <div className="sync-indicator">
          {isSynchronized ? (
            <span className="synced">✓ Ready to submit</span>
          ) : (
            <span className="syncing">⏳ Synchronizing changes...</span>
          )}
        </div>
      </div>
      
      <form onSubmit={handleSubmit} className="form-body">
        <div className="field">
          <label htmlFor="name">Name *</label>
          <input
            id="name"
            type="text"
            value={formData.name}
            onChange={(e) => updateField('name', e.target.value)}
            className={!isSynchronized ? 'syncing' : ''}
            required
          />
        </div>
        
        <div className="field">
          <label htmlFor="email">Email *</label>
          <input
            id="email"
            type="email"
            value={formData.email}
            onChange={(e) => updateField('email', e.target.value)}
            className={!isSynchronized ? 'syncing' : ''}
            required
          />
        </div>
        
        <div className="field">
          <label htmlFor="message">Message *</label>
          <textarea
            id="message"
            value={formData.message}
            onChange={(e) => updateField('message', e.target.value)}
            className={!isSynchronized ? 'syncing' : ''}
            rows={4}
            required
          />
        </div>
        
        <div className="form-actions">
          <button
            type="submit"
            disabled={!canSubmit}
            className={canSubmit ? 'ready' : 'disabled'}
          >
            {isSubmitting ? (
              <>
                <span className="spinner"></span>
                Submitting...
              </>
            ) : !isSynchronized ? (
              'Waiting for sync...'
            ) : (
              'Submit Form'
            )}
          </button>
          
          {!isSynchronized && (
            <p className="sync-warning">
              Form submission is disabled while synchronizing changes with other users.
            </p>
          )}
        </div>
      </form>
    </div>
  )
}

Real-time Collaboration Editor

Show sync status in a collaborative text editor:

import { useIsSynchronized, useStateTogether, useConnectedUsers } from 'react-together'
import { useState, useRef, useEffect } from 'react'

function CollaborativeEditor() {
  const isSynchronized = useIsSynchronized()
  const [content, setContent] = useStateTogether('document', '')
  const connectedUsers = useConnectedUsers()
  const [lastSyncTime, setLastSyncTime] = useState<Date | null>(null)
  const textareaRef = useRef<HTMLTextAreaElement>(null)
  
  // Update last sync time when synchronized
  useEffect(() => {
    if (isSynchronized) {
      setLastSyncTime(new Date())
    }
  }, [isSynchronized])
  
  const formatSyncTime = (time: Date | null) => {
    if (!time) return 'Never'
    const now = new Date()
    const diff = now.getTime() - time.getTime()
    
    if (diff < 1000) return 'Just now'
    if (diff < 60000) return `${Math.floor(diff / 1000)}s ago`
    if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`
    return time.toLocaleTimeString()
  }
  
  return (
    <div className="collaborative-editor">
      <div className="editor-header">
        <div className="document-title">
          <h2>Shared Document</h2>
          <span className="character-count">
            {content.length} characters
          </span>
        </div>
        
        <div className="editor-status">
          <div className="sync-status">
            <div className={`sync-indicator ${isSynchronized ? 'synced' : 'syncing'}`}>
              <div className="status-dot"></div>
              <span className="status-text">
                {isSynchronized ? 'Synchronized' : 'Synchronizing...'}
              </span>
            </div>
            <div className="last-sync">
              Last sync: {formatSyncTime(lastSyncTime)}
            </div>
          </div>
          
          <div className="user-indicator">
            <span className="user-count">
              {connectedUsers.length} user(s) editing
            </span>
            <div className="user-avatars">
              {connectedUsers.slice(0, 3).map(user => (
                <div
                  key={user.userId}
                  className={`user-avatar ${user.isYou ? 'you' : ''}`}
                  title={user.nickname}
                >
                  {user.nickname.charAt(0).toUpperCase()}
                </div>
              ))}
              {connectedUsers.length > 3 && (
                <div className="user-avatar overflow">
                  +{connectedUsers.length - 3}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
      
      <div className="editor-body">
        <textarea
          ref={textareaRef}
          value={content}
          onChange={(e) => setContent(e.target.value)}
          className={`document-textarea ${!isSynchronized ? 'syncing' : ''}`}
          placeholder="Start typing to collaborate with others..."
          rows={20}
        />
        
        {!isSynchronized && (
          <div className="sync-overlay">
            <div className="sync-message">
              <span className="spinner"></span>
              Synchronizing your changes...
            </div>
          </div>
        )}
      </div>
      
      <div className="editor-footer">
        <div className="sync-info">
          {isSynchronized ? (
            <span className="sync-complete">
              ✓ All changes synchronized across {connectedUsers.length} user(s)
            </span>
          ) : (
            <span className="sync-pending">
              ⏳ Synchronizing changes with other users...
            </span>
          )}
        </div>
        
        <div className="auto-save-info">
          Changes are automatically saved and shared in real-time
        </div>
      </div>
    </div>
  )
}

Best Practices

Debounced Sync Indicator

Avoid flickering sync indicators with proper debouncing:

import { useIsSynchronized } from 'react-together'
import { useState, useEffect } from 'react'

function useDebouncedSyncStatus(delay = 100) {
  const isSynchronized = useIsSynchronized()
  const [debouncedSynced, setDebouncedSynced] = useState(isSynchronized)
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedSynced(isSynchronized)
    }, delay)
    
    return () => clearTimeout(timer)
  }, [isSynchronized, delay])
  
  return debouncedSynced
}

Sync State Management

Track sync state changes for analytics:

import { useIsSynchronized } from 'react-together'
import { useEffect, useRef } from 'react'

function useSyncAnalytics() {
  const isSynchronized = useIsSynchronized()
  const lastSyncRef = useRef<boolean>(isSynchronized)
  const syncStartTimeRef = useRef<Date | null>(null)
  
  useEffect(() => {
    if (lastSyncRef.current !== isSynchronized) {
      if (!isSynchronized) {
        // Sync started
        syncStartTimeRef.current = new Date()
      } else if (syncStartTimeRef.current) {
        // Sync completed
        const duration = new Date().getTime() - syncStartTimeRef.current.getTime()
        console.log(`Synchronization completed in ${duration}ms`)
        syncStartTimeRef.current = null
      }
      
      lastSyncRef.current = isSynchronized
    }
  }, [isSynchronized])
  
  return isSynchronized
}

Common Patterns

  • Loading States: Show spinners or disabled states while not synchronized
  • Optimistic Updates: Update UI immediately, show pending state until synced
  • Form Validation: Prevent submission until changes are synchronized
  • Auto-save Indicators: Show when documents are being saved/synced
  • Conflict Resolution: Handle cases where sync takes longer than expected

TypeScript Support

useIsSynchronized is fully typed and returns a boolean value:

import { useIsSynchronized } from 'react-together'

const isSynchronized: boolean = useIsSynchronized()

Technical Notes

The hook checks three conditions: session connection, presence of at least one user with state, and model synchronization with the Croquet reflector. Heavy model processing can temporarily cause false returns.

Synchronization status can change rapidly during heavy usage. Use debouncing techniques to avoid UI flickering and provide smooth user experiences.