Check if your local state is synchronized with the shared model. Essential for building responsive real-time interfaces.
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.
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>
)
}
useIsSynchronized(): boolean
true
if connected to a session and all state is synchronized with the model, false
during sync or when disconnected
The hook returns true
when all of these conditions are met:
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>
)
}
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>
)
}
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>
)
}
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>
)
}
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>
)
}
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
}
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
}
useIsTogether
- Check if connected to sessionuseStateTogether
- The state being synchronizeduseConnectedUsers
- Other users in the sessionuseNicknames
- User identification during syncuseIsSynchronized
is fully typed and returns a boolean value:
import { useIsSynchronized } from 'react-together'
const isSynchronized: boolean = useIsSynchronized()
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.