본문으로 건너뛰기

LocalStorage 컬렉션

LocalStorage 컬렉션은 브라우저 세션 간에 유지되고 브라우저 탭 간에 실시간으로 동기화되는 소량의 로컬 전용 상태를 저장합니다.

개요

localStorageCollectionOptions을 사용하면 다음과 같은 컬렉션을 생성할 수 있습니다:

  • 데이터를 localStorage(또는 sessionStorage)에 영속화합니다
  • storage 이벤트를 사용하여 브라우저 탭 간에 자동으로 동기화합니다
  • 오류 발생 시 자동 롤백을 지원하는 낙관적 업데이트를 지원합니다
  • 모든 데이터를 하나의 localStorage 키 아래에 저장합니다
  • localStorage 인터페이스와 일치하는 모든 스토리지 API와 함께 작동합니다

설치

LocalStorage 컬렉션은 핵심 TanStack DB 패키지에 포함되어 있습니다:

npm install @tanstack/react-db

기본 사용법

import { createCollection } from '@tanstack/react-db'
import { localStorageCollectionOptions } from '@tanstack/react-db'

const userPreferencesCollection = createCollection(
localStorageCollectionOptions({
id: 'user-preferences',
storageKey: 'app-user-prefs',
getKey: (item) => item.id,
})
)

로컬 뮤테이션 직접 수행

중요: LocalStorage 컬렉션은 서버와 동기화되는 컬렉션과 다르게 작동합니다. LocalStorage 컬렉션에서는 collection.insert(), collection.update(), collection.delete()과 같은 메서드를 호출하여 상태를 직접 뮤테이션하면 됩니다. 이것이 필요한 작업의 전부입니다. 변경 사항은 로컬 데이터에 즉시 적용되고 localStorage에 자동으로 영속화됩니다.

이는 뮤테이션 핸들러가 데이터를 백엔드로 전송하는 서버 동기화 컬렉션(Query Collection 등)과 다릅니다. LocalStorage 컬렉션에서는 모든 것이 로컬에 유지됩니다:

// Just call the methods directly - automatically persisted to localStorage
userPreferencesCollection.insert({ id: 'theme', mode: 'dark' })
userPreferencesCollection.update('theme', (draft) => { draft.mode = 'light' })
userPreferencesCollection.delete('theme')

구성 옵션

localStorageCollectionOptions 함수는 다음 옵션을 허용합니다:

필수 옵션

  • id: 컬렉션의 고유 식별자입니다
  • storageKey: 모든 컬렉션 데이터가 저장되는 localStorage 키입니다
  • getKey: 항목에서 고유 키를 추출하는 함수입니다

선택적 옵션

  • schema: 클라이언트 측 유효성 검사를 위한 Standard Schema 호환 스키마입니다(예: Zod, Effect)
  • storage: 사용자 지정 스토리지 구현입니다(기본값은 localStorage). sessionStorage이거나 localStorage API를 사용하는 모든 객체일 수 있습니다
  • storageEventApi: 스토리지 이벤트를 구독하기 위한 이벤트 API입니다(기본값은 window). 사용자 지정 탭 간, 창 간 또는 프로세스 간 동기화를 활성화합니다
  • onInsert: 항목이 삽입될 때 호출되는 선택적 핸들러 함수입니다
  • onUpdate: 항목이 업데이트될 때 호출되는 선택적 핸들러 함수입니다
  • onDelete: 항목이 삭제될 때 호출되는 선택적 핸들러 함수입니다

탭 간 동기화

LocalStorage 컬렉션은 브라우저 탭 간에 실시간으로 자동 동기화됩니다:

const settingsCollection = createCollection(
localStorageCollectionOptions({
id: 'settings',
storageKey: 'app-settings',
getKey: (item) => item.id,
})
)

// Changes in one tab are automatically reflected in all other tabs
// This works automatically via storage events

SessionStorage 사용

세션 전용 영속성에는 sessionStorage을 사용할 수 있습니다. 이는 localStorage을 대신합니다:

const sessionCollection = createCollection(
localStorageCollectionOptions({
id: 'session-data',
storageKey: 'session-key',
storage: sessionStorage, // Use sessionStorage instead
getKey: (item) => item.id,
})
)

사용자 지정 스토리지 백엔드

localStorage API와 일치하는 모든 스토리지 구현을 제공할 수 있습니다:

// Example: Custom storage wrapper with encryption
const encryptedStorage = {
getItem(key: string) {
const encrypted = localStorage.getItem(key)
return encrypted ? decrypt(encrypted) : null
},
setItem(key: string, value: string) {
localStorage.setItem(key, encrypt(value))
},
removeItem(key: string) {
localStorage.removeItem(key)
},
}

const secureCollection = createCollection(
localStorageCollectionOptions({
id: 'secure-data',
storageKey: 'encrypted-key',
storage: encryptedStorage,
getKey: (item) => item.id,
})
)

사용자 지정 스토리지를 사용한 탭 간 동기화

storageEventApi 옵션(기본값은 window)을 사용하면 컬렉션이 탭 간 동기화를 위해 스토리지 이벤트를 구독할 수 있습니다. 사용자 지정 스토리지 구현은 다음 API를 제공하여 사용자 지정 탭 간, 창 간 또는 프로세스 간 동기화를 활성화할 수 있습니다:

// Example: Custom storage event API for cross-process sync
const customStorageEventApi = {
addEventListener(event: string, handler: (e: StorageEvent) => void) {
// Custom event subscription logic
// Could be IPC, WebSocket, or any other mechanism
myCustomEventBus.on('storage-change', handler)
},
removeEventListener(event: string, handler: (e: StorageEvent) => void) {
myCustomEventBus.off('storage-change', handler)
},
}

const syncedCollection = createCollection(
localStorageCollectionOptions({
id: 'synced-data',
storageKey: 'data-key',
storage: customStorage,
storageEventApi: customStorageEventApi, // Custom event API
getKey: (item) => item.id,
})
)

이를 통해 브라우저 탭을 넘어 다음과 같이 서로 다른 컨텍스트 간 동기화를 활성화할 수 있습니다:

  • Electron 앱에서의 프로세스 간 통신
  • 여러 브라우저 창 간 WebSocket 기반 동기화
  • 데스크톱 애플리케이션의 사용자 지정 IPC 메커니즘

뮤테이션 핸들러

뮤테이션 핸들러는 완전히 선택 사항입니다. 핸들러를 제공하는지 여부와 관계없이 데이터는 localStorage에 영속화됩니다:

const preferencesCollection = createCollection(
localStorageCollectionOptions({
id: 'preferences',
storageKey: 'user-prefs',
getKey: (item) => item.id,
// Optional: Add custom logic when preferences are updated
onUpdate: async ({ transaction }) => {
const { modified } = transaction.mutations[0]
console.log('Preference updated:', modified)
// Maybe send analytics or trigger other side effects
},
})
)

수동 트랜잭션

수동 트랜잭션(createTransaction을 통해 생성)과 함께 LocalStorage 컬렉션을 사용할 때는 변경 사항을 영속화하기 위해 utils.acceptMutations()을 호출해야 합니다:

import { createTransaction } from '@tanstack/react-db'

const localData = createCollection(
localStorageCollectionOptions({
id: 'form-draft',
storageKey: 'draft-data',
getKey: (item) => item.id,
})
)

const serverCollection = createCollection(
queryCollectionOptions({
queryKey: ['items'],
queryFn: async () => api.items.getAll(),
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
await api.items.create(transaction.mutations[0].modified)
},
})
)

const tx = createTransaction({
mutationFn: async ({ transaction }) => {
// Handle server collection mutations explicitly in mutationFn
await Promise.all(
transaction.mutations
.filter((m) => m.collection === serverCollection)
.map((m) => api.items.create(m.modified))
)

// After server mutations succeed, persist local collection mutations
localData.utils.acceptMutations(transaction)
},
})

// Apply mutations to both collections in one transaction
tx.mutate(() => {
localData.insert({ id: 'draft-1', data: '...' })
serverCollection.insert({ id: '1', name: 'Item' })
})

await tx.commit()

완전한 예제

import { createCollection, eq } from '@tanstack/react-db'
import { localStorageCollectionOptions } from '@tanstack/react-db'
import { useLiveQuery } from '@tanstack/react-db'
import { z } from 'zod'

// Define schema
const userPrefsSchema = z.object({
id: z.string(),
theme: z.enum(['light', 'dark', 'auto']),
language: z.string(),
notifications: z.boolean(),
})

type UserPrefs = z.infer<typeof userPrefsSchema>

// Create collection
export const userPreferencesCollection = createCollection(
localStorageCollectionOptions({
id: 'user-preferences',
storageKey: 'app-user-prefs',
getKey: (item) => item.id,
schema: userPrefsSchema,
})
)

// Use in component
function SettingsPanel() {
const { data: prefs } = useLiveQuery({
query: (q) =>
q
.from({ pref: userPreferencesCollection })
.where(({ pref }) => eq(pref.id, 'current-user')),
})

const currentPrefs = prefs[0]

const updateTheme = (theme: 'light' | 'dark' | 'auto') => {
if (currentPrefs) {
userPreferencesCollection.update(currentPrefs.id, (draft) => {
draft.theme = theme
})
} else {
userPreferencesCollection.insert({
id: 'current-user',
theme,
language: 'en',
notifications: true,
})
}
}

return (
<div>
<h2>Theme: {currentPrefs?.theme}</h2>
<button onClick={() => updateTheme('dark')}>Dark Mode</button>
<button onClick={() => updateTheme('light')}>Light Mode</button>
</div>
)
}

사용 사례

LocalStorage 컬렉션은 다음에 적합합니다:

  • 사용자 기본 설정 및 설정
  • 세션 간에 유지되어야 하는 UI 상태
  • 양식 초안
  • 최근 본 항목
  • 사용자별 구성
  • 소량의 캐시 데이터

자세히 알아보기