Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/LegendApp/legend-state/llms.txt

Use this file to discover all available pages before exploring further.

The AsyncStorage plugin persists observable data to React Native’s AsyncStorage. This is the standard persistence solution for React Native applications.

Installation

npm install @legendapp/state @react-native-async-storage/async-storage

Setup

import { configureObservableSync } from '@legendapp/state/sync'
import { ObservablePersistAsyncStorage } from '@legendapp/state/persist-plugins/async-storage'
import AsyncStorage from '@react-native-async-storage/async-storage'

configureObservableSync({
  persist: {
    plugin: ObservablePersistAsyncStorage,
    asyncStorage: {
      AsyncStorage
    }
  }
})

Configuration

asyncStorage.AsyncStorage
AsyncStorageStatic
required
The AsyncStorage instance from @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage'

asyncStorage: {
  AsyncStorage
}
asyncStorage.preload
boolean | string[]
Preload data on initialization for faster startup
// Preload all keys
asyncStorage: {
  AsyncStorage,
  preload: true
}

// Preload specific keys
asyncStorage: {
  AsyncStorage,
  preload: ['user', 'settings', 'cart']
}

Usage

Basic Usage

import { synced } from '@legendapp/state/sync'

const user$ = synced({
  get: () => api.getUser(),
  persist: {
    name: 'user'
    // Uses globally configured AsyncStorage plugin
  }
})

With Preload

import AsyncStorage from '@react-native-async-storage/async-storage'
import { configureObservableSync } from '@legendapp/state/sync'
import { ObservablePersistAsyncStorage } from '@legendapp/state/persist-plugins/async-storage'

configureObservableSync({
  persist: {
    plugin: ObservablePersistAsyncStorage,
    asyncStorage: {
      AsyncStorage,
      preload: ['user', 'settings']  // Fast startup
    }
  }
})

const user$ = synced({
  get: () => api.getUser(),
  persist: { name: 'user' }
})

const settings$ = synced({
  initial: { theme: 'light' },
  persist: { name: 'settings' }
})

// Both are already loaded from preload

Plugin API

The AsyncStorage plugin implements the ObservablePersistPlugin interface:

initialize()

async initialize(config: ObservablePersistPluginOptions): Promise<void>
Initializes the plugin and preloads data if configured.

loadTable()

async loadTable(table: string): Promise<void>
Loads a specific key from AsyncStorage.

getTable()

getTable<T>(table: string, init: object): T
Gets the cached data for a key.

set()

async set(table: string, changes: Change[]): Promise<void>
Applies changes and saves to AsyncStorage.

getMetadata()

getMetadata(table: string): PersistMetadata
Retrieves sync metadata.

setMetadata()

async setMetadata(table: string, metadata: PersistMetadata): Promise<void>
Saves sync metadata.

deleteTable()

async deleteTable(table: string): Promise<void>
Removes data from AsyncStorage.

deleteMetadata()

async deleteMetadata(table: string): Promise<void>
Removes metadata from AsyncStorage.

Examples

Complete App Setup

import React from 'react'
import { configureObservableSync } from '@legendapp/state/sync'
import { ObservablePersistAsyncStorage } from '@legendapp/state/persist-plugins/async-storage'
import AsyncStorage from '@react-native-async-storage/async-storage'

// Configure at app startup
configureObservableSync({
  persist: {
    plugin: ObservablePersistAsyncStorage,
    asyncStorage: {
      AsyncStorage,
      preload: true  // Preload all keys
    }
  }
})

export default function App() {
  return <YourApp />
}

User Session

import { synced } from '@legendapp/state/sync'
import { when } from '@legendapp/state'

const user$ = synced({
  get: () => api.getUser(),
  persist: { name: 'user' }
})

// Wait for user to load
await when(syncState(user$).isPersistLoaded)

if (user$.get()) {
  // User is logged in
  navigateToHome()
} else {
  // No user, show login
  navigateToLogin()
}

App Settings

import { synced } from '@legendapp/state/sync'
import { observer } from '@legendapp/state/react'

const settings$ = synced({
  initial: {
    theme: 'light',
    notifications: true,
    language: 'en'
  },
  persist: { name: 'app-settings' }
})

const SettingsScreen = observer(function SettingsScreen() {
  return (
    <View>
      <Switch
        value={settings$.theme.get() === 'dark'}
        onValueChange={(value) => 
          settings$.theme.set(value ? 'dark' : 'light')
        }
      />
      <Text>Dark Mode</Text>
    </View>
  )
})

Shopping Cart

import { synced } from '@legendapp/state/sync'

interface CartItem {
  id: string
  name: string
  quantity: number
  price: number
}

const cart$ = synced<{ items: CartItem[] }>({
  initial: { items: [] },
  persist: { name: 'shopping-cart' }
})

// Add to cart
function addToCart(item: CartItem) {
  const items = cart$.items.get()
  const existing = items.find(i => i.id === item.id)
  
  if (existing) {
    existing.quantity++
    cart$.items.set([...items])
  } else {
    cart$.items.push(item)
  }
}

// Clear cart
function clearCart() {
  cart$.items.set([])
}

Multi-Account

const currentUserId$ = observable<string | null>(null)

const userData$ = synced({
  get: async () => {
    const userId = currentUserId$.get()
    if (!userId) return null
    return api.getUserData(userId)
  },
  persist: {
    name: computed(() => {
      const userId = currentUserId$.get()
      return userId ? `user-data-${userId}` : 'user-data'
    })
  }
})

// Switch accounts
currentUserId$.set('user-123')
// Loads user-data-user-123 from AsyncStorage

Offline Support

import { synced } from '@legendapp/state/sync'
import { observer } from '@legendapp/state/react'
import NetInfo from '@react-native-community/netinfo'

const isOnline$ = observable(true)

NetInfo.addEventListener(state => {
  isOnline$.set(state.isConnected ?? false)
})

const todos$ = synced({
  get: () => api.getTodos(),
  set: ({ value }) => api.saveTodos(value),
  persist: {
    name: 'todos',
    retrySync: true  // Retry when back online
  },
  waitFor: () => isOnline$.get()  // Wait for connection
})

const TodoScreen = observer(function TodoScreen() {
  if (!isOnline$.get()) {
    return <Text>Offline - changes will sync when online</Text>
  }
  
  return (
    <FlatList
      data={todos$.get()}
      renderItem={({ item }) => <TodoItem item={item} />}
    />
  )
})

Clear User Data on Logout

import { syncState } from '@legendapp/state/sync'

const user$ = synced({
  persist: { name: 'user' }
})

const cart$ = synced({
  persist: { name: 'cart' }
})

async function logout() {
  // Clear persisted data
  await Promise.all([
    syncState(user$).resetPersistence(),
    syncState(cart$).resetPersistence()
  ])
  
  // Reset observables
  user$.set(null)
  cart$.set({ items: [] })
  
  // Navigate to login
  navigation.navigate('Login')
}

Storage Format

Data is stored as JSON strings:
const user$ = synced({
  initial: { name: 'John', email: 'john@example.com' },
  persist: { name: 'user' }
})

// AsyncStorage contents:
// key: 'user'
// value: '{"name":"John","email":"john@example.com"}'

// Metadata stored separately:
// key: 'user__m'
// value: '{"lastSync":1699564800000,"pending":{}}'

Performance

Without Preload

// Each observable loads individually (slower startup)
configureObservableSync({
  persist: {
    plugin: ObservablePersistAsyncStorage,
    asyncStorage: { AsyncStorage }
  }
})

const user$ = synced({ persist: { name: 'user' } })
const settings$ = synced({ persist: { name: 'settings' } })
const cart$ = synced({ persist: { name: 'cart' } })

// 3 separate AsyncStorage.getItem() calls

With Preload

// Load all keys at once (faster startup)
configureObservableSync({
  persist: {
    plugin: ObservablePersistAsyncStorage,
    asyncStorage: {
      AsyncStorage,
      preload: ['user', 'settings', 'cart']
    }
  }
})

const user$ = synced({ persist: { name: 'user' } })
const settings$ = synced({ persist: { name: 'settings' } })
const cart$ = synced({ persist: { name: 'cart' } })

// 1 AsyncStorage.multiGet() call

Best Practices

  1. Use preload: Load critical data on app startup
  2. Namespace keys: Use descriptive, unique names
  3. Handle errors: AsyncStorage operations can fail
  4. Consider MMKV: For better performance, use MMKV plugin
  5. Clear on logout: Remove sensitive data when user logs out

When to Use

Use AsyncStorage when:
  • Standard React Native persistence is sufficient
  • Data size is moderate (< 6MB)
  • You don’t need synchronous access
  • Cross-platform compatibility is important
Use MMKV when:
  • You need better performance
  • Synchronous access is required
  • Data is accessed frequently

See Also

Build docs developers (and LLMs) love