Skip to main content
The syncObservable() function adds synchronization and persistence to an existing observable. Use this when you already have an observable and want to add sync capabilities, or when you need more control over the observable creation.

Usage

import { observable } from '@legendapp/state'
import { syncObservable } from '@legendapp/state/sync'

const data$ = observable({ items: [] })

const syncState$ = syncObservable(data$, {
  get: () => fetch('/api/items').then(r => r.json()),
  set: ({ value }) => fetch('/api/items', {
    method: 'POST',
    body: JSON.stringify(value)
  }),
  persist: {
    name: 'items',
    plugin: ObservablePersistLocalStorage
  }
})

Parameters

obs$
Observable<T>
required
The observable to add sync capabilities to
options
SyncedOptions<T>
required
Sync configuration options. Same as synced() options.

Return Value

Returns an Observable<ObservableSyncState> that tracks the sync status:
interface ObservableSyncState {
  // Loading states
  isLoaded: Observable<boolean>
  isPersistLoaded: Observable<boolean>
  isLoading: Observable<boolean>
  
  // Sync states
  isSyncEnabled: Observable<boolean>
  isPersistEnabled: Observable<boolean>
  
  // Active operations
  isSetting: Observable<boolean>
  numPendingSets: Observable<number>
  numPendingLocalLoads: Observable<number>
  numPendingRemoteLoads: Observable<number>
  
  // Timing
  lastSync: Observable<number | undefined>
  
  // Error handling
  error: Observable<Error | undefined>
  
  // Control functions
  sync: (options?: ObservableSyncStateOptions) => Promise<void>
  getPendingChanges: () => PendingChanges | undefined
  
  // Lifecycle
  clearPersist: () => Promise<void>  // DEPRECATED: use resetPersistence
  resetPersistence: () => Promise<void>
}

ObservableSyncState Fields

isLoaded
Observable<boolean>
true when all remote loads have completed
isPersistLoaded
Observable<boolean>
true when local persistence has loaded
isLoading
Observable<boolean>
true when actively loading from remote
isSyncEnabled
Observable<boolean>
Enable/disable remote syncing
// Disable syncing temporarily
syncState$.isSyncEnabled.set(false)
isPersistEnabled
Observable<boolean>
Enable/disable local persistence
isSetting
Observable<boolean>
true when actively saving to remote
numPendingSets
Observable<number>
Number of pending save operations
numPendingLocalLoads
Observable<number>
Number of pending local persistence loads
numPendingRemoteLoads
Observable<number>
Number of pending remote loads
lastSync
Observable<number | undefined>
Timestamp of the last successful sync
error
Observable<Error | undefined>
Most recent error from sync operations
sync
(options?: ObservableSyncStateOptions) => Promise<void>
Manually trigger a sync
// Refresh from remote
await syncState$.sync()

// Force refresh ignoring lastSync
await syncState$.sync({ resetLastSync: true })
getPendingChanges
() => PendingChanges | undefined
Get pending changes that haven’t synced yetReturns an object with paths as keys and change info as values:
{
  "path/to/field": {
    p: previousValue,
    v: newValue,
    t: pathTypes
  }
}
resetPersistence
() => Promise<void>
Clear all persisted data for this observable (both data and metadata)
await syncState$.resetPersistence()

Advanced Examples

Monitoring Sync State

const data$ = observable({ count: 0 })
const syncState$ = syncObservable(data$, {
  get: () => api.getData(),
  persist: { name: 'data', plugin: ObservablePersistLocalStorage }
})

// Show loading indicator
observe(() => {
  if (syncState$.isLoading.get()) {
    showSpinner()
  } else {
    hideSpinner()
  }
})

// Show save status
observe(() => {
  if (syncState$.isSetting.get()) {
    console.log('Saving...')
  } else {
    console.log('All changes saved')
  }
})

// Handle errors
observe(() => {
  const error = syncState$.error.get()
  if (error) {
    showErrorMessage(error.message)
  }
})

Manual Sync Control

const data$ = observable({ items: [] })
const syncState$ = syncObservable(data$, {
  get: () => api.getItems(),
  syncMode: 'manual',  // Don't sync automatically
  persist: { name: 'items', plugin: ObservablePersistLocalStorage }
})

// Sync on button click
button.onclick = () => {
  syncState$.sync()
}

// Or with refresh
refreshButton.onclick = () => {
  syncState$.sync({ resetLastSync: true })
}

Conditional Syncing

const data$ = observable({ value: 0 })
const syncState$ = syncObservable(data$, {
  get: () => api.getData(),
  persist: { name: 'data', plugin: ObservablePersistLocalStorage }
})

// Disable syncing during offline mode
const isOnline$ = observable(navigator.onLine)
window.addEventListener('online', () => isOnline$.set(true))
window.addEventListener('offline', () => isOnline$.set(false))

observe(() => {
  syncState$.isSyncEnabled.set(isOnline$.get())
})

// Manually sync when coming back online
observe(() => {
  if (isOnline$.get()) {
    syncState$.sync()
  }
})

Pending Changes Tracking

const form$ = observable({ name: '', email: '' })
const syncState$ = syncObservable(form$, {
  set: ({ value }) => api.saveForm(value),
  debounceSet: 1000,
  persist: { name: 'form', plugin: ObservablePersistLocalStorage, retrySync: true }
})

// Show unsaved changes indicator
observe(() => {
  const pending = syncState$.getPendingChanges()
  const hasPending = pending && Object.keys(pending).length > 0
  
  unsavedIndicator.style.display = hasPending ? 'block' : 'none'
})

// Show what's pending
button.onclick = () => {
  const pending = syncState$.getPendingChanges()
  console.log('Unsaved changes:', pending)
}

Syncing Multiple Observables

const user$ = observable({ name: '', email: '' })
const settings$ = observable({ theme: 'light', notifications: true })

const userSync$ = syncObservable(user$, {
  get: () => api.getUser(),
  set: ({ value }) => api.saveUser(value),
  persist: { name: 'user', plugin: ObservablePersistLocalStorage }
})

const settingsSync$ = syncObservable(settings$, {
  get: () => api.getSettings(),
  set: ({ value }) => api.saveSettings(value),
  persist: { name: 'settings', plugin: ObservablePersistLocalStorage }
})

// Wait for both to load
await Promise.all([
  when(userSync$.isLoaded),
  when(settingsSync$.isLoaded)
])

Reset and Clear Data

const data$ = observable({ items: [] })
const syncState$ = syncObservable(data$, {
  get: () => api.getData(),
  persist: { name: 'data', plugin: ObservablePersistLocalStorage }
})

// Clear persisted data and metadata
logoutButton.onclick = async () => {
  await syncState$.resetPersistence()
  data$.set({ items: [] })  // Reset observable
}

Differences from synced()

FeaturesyncObservable()synced()
InputExisting observableCreates new observable
ReturnSync state observableSynced observable
Use CaseAdd sync to existing observableCreate synced observable
ControlFull control over observableObservable created for you
// syncObservable - you control the observable
const data$ = observable({ items: [] })
const syncState$ = syncObservable(data$, options)

// synced - observable is created for you
const data$ = synced(options)

See Also

Build docs developers (and LLMs) love