LocalOnly 컬렉션
LocalOnly 컬렉션은 브라우저 세션 간에 영속화하거나 탭 간에 동기화할 필요가 없는 메모리 내 클라이언트 데이터 또는 UI 상태를 위해 설계되었습니다.
개요
localOnlyCollectionOptions을 사용하면 다음과 같은 컬렉션을 만들 수 있습니다:
- 메모리에만 데이터를 저장합니다(영속성 없음)
- 오류 발생 시 자동 롤백을 사용하는 낙관적 업데이트를 지원합니다
- 선택적 초기 데이터를 제공합니다
- 임시 UI 상태와 세션 전용 데이터에 완벽하게 작동합니다
- 낙관적 상태에서 확인된 상태로의 전환을 자동으로 관리합니다
설치
LocalOnly 컬렉션은 핵심 TanStack DB 패키지에 포함되어 있습니다:
npm install @tanstack/react-db
기본 사용법
import { createCollection } from '@tanstack/react-db'
import { localOnlyCollectionOptions } from '@tanstack/react-db'
const uiStateCollection = createCollection(
localOnlyCollectionOptions({
id: 'ui-state',
getKey: (item) => item.id,
})
)
로컬 뮤테이션 직접 수행
중요: LocalOnly 컬렉션은 서버와 동기화되는 컬렉션과 다르게 작동합니다. LocalOnly 컬렉션에서는 collection.insert(), collection.update() 및 collection.delete()와 같은 메서드를 호출하여 상태를 직접 뮤테이션하면 됩니다. 변경 사항은 로컬 메모리 내 데이터에 즉시 적용됩니다.
이는 서버와 동기화되는 컬렉션(Query Collection 등)과 다릅니다. 이러한 컬렉션에서는 뮤테이션 핸들러가 데이터를 백엔드로 전송합니다. LocalOnly 컬렉션에서는 모든 것이 로컬에 유지됩니다:
// Just call the methods directly - no server sync involved
uiStateCollection.insert({ id: 'theme', mode: 'dark' })
uiStateCollection.update('theme', (draft) => { draft.mode = 'light' })
uiStateCollection.delete('theme')
구성 옵션
localOnlyCollectionOptions 함수는 다음 옵션을 허용합니다:
필수 옵션
id: 컬렉션의 고유 식별자입니다getKey: 항목에서 고유 키를 추출하는 함수입니다
선택적 옵션
schema: 클라이언트 측 검증을 위한 Standard Schema 호환 스키마입니다(예: Zod, Effect)initialData: 생성 시 컬렉션에 채울 항목 배열입니다onInsert: 삽입을 확인하기 전에 호출되는 선택적 핸들러 함수입니다onUpdate: 업데이트를 확인하기 전에 호출되는 선택적 핸들러 함수입니다onDelete: 삭제를 확인하기 전에 호출되는 선택적 핸들러 함수입니다
초기 데이터
생성 시 컬렉션에 초기 데이터를 채웁니다:
const uiStateCollection = createCollection(
localOnlyCollectionOptions({
id: 'ui-state',
getKey: (item) => item.id,
initialData: [
{ id: 'sidebar', isOpen: false },
{ id: 'theme', mode: 'light' },
{ id: 'modal', visible: false },
],
})
)
뮤테이션 핸들러
뮤테이션 핸들러는 완전히 선택 사항입니다. 제공된 경우 낙관적 상태가 확인되기 전에 호출됩니다:
const tempDataCollection = createCollection(
localOnlyCollectionOptions({
id: 'temp-data',
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
// Custom logic before confirming the insert
console.log('Inserting:', transaction.mutations[0].modified)
},
onUpdate: async ({ transaction }) => {
// Custom logic before confirming the update
const { original, modified } = transaction.mutations[0]
console.log('Updating from', original, 'to', modified)
},
onDelete: async ({ transaction }) => {
// Custom logic before confirming the delete
console.log('Deleting:', transaction.mutations[0].original)
},
})
)
수동 트랜잭션
수동 트랜잭션( createTransaction을 통해 생성)을 사용하여 LocalOnly 컬렉션을 작업할 때는 변경 사항을 영속화하기 위해 utils.acceptMutations()를 호출해야 합니다:
import { createTransaction } from '@tanstack/react-db'
const localData = createCollection(
localOnlyCollectionOptions({
id: 'form-draft',
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, accept 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 { localOnlyCollectionOptions } from '@tanstack/react-db'
import { useLiveQuery } from '@tanstack/react-db'
import { z } from 'zod'
// Define schema
const modalStateSchema = z.object({
id: z.string(),
isOpen: z.boolean(),
data: z.any().optional(),
})
type ModalState = z.infer<typeof modalStateSchema>
// Create collection
export const modalStateCollection = createCollection(
localOnlyCollectionOptions({
id: 'modal-state',
getKey: (item) => item.id,
schema: modalStateSchema,
initialData: [
{ id: 'user-profile', isOpen: false },
{ id: 'settings', isOpen: false },
{ id: 'confirm-delete', isOpen: false },
],
})
)
// Use in component
function UserProfileModal() {
const { data: modals } = useLiveQuery({
query: (q) =>
q
.from({ modal: modalStateCollection })
.where(({ modal }) => eq(modal.id, 'user-profile')),
})
const modalState = modals[0]
const openModal = (data?: any) => {
modalStateCollection.update('user-profile', (draft) => {
draft.isOpen = true
draft.data = data
})
}
const closeModal = () => {
modalStateCollection.update('user-profile', (draft) => {
draft.isOpen = false
draft.data = undefined
})
}
if (!modalState?.isOpen) return null
return (
<div className="modal">
<h2>User Profile</h2>
<pre>{JSON.stringify(modalState.data, null, 2)}</pre>
<button onClick={closeModal}>Close</button>
</div>
)
}
완전한 예제: 양식 초안 상태
import { createCollection, eq } from '@tanstack/react-db'
import { localOnlyCollectionOptions } from '@tanstack/react-db'
import { useLiveQuery } from '@tanstack/react-db'
type FormDraft = {
id: string
formData: Record<string, any>
lastModified: Date
}
// Create collection for form drafts
export const formDraftsCollection = createCollection(
localOnlyCollectionOptions({
id: 'form-drafts',
getKey: (item) => item.id,
})
)
// Use in component
function CreatePostForm() {
const { data: drafts } = useLiveQuery({
query: (q) =>
q
.from({ draft: formDraftsCollection })
.where(({ draft }) => eq(draft.id, 'new-post')),
})
const currentDraft = drafts[0]
const updateDraft = (field: string, value: any) => {
if (currentDraft) {
formDraftsCollection.update('new-post', (draft) => {
draft.formData[field] = value
draft.lastModified = new Date()
})
} else {
formDraftsCollection.insert({
id: 'new-post',
formData: { [field]: value },
lastModified: new Date(),
})
}
}
const clearDraft = () => {
if (currentDraft) {
formDraftsCollection.delete('new-post')
}
}
const submitForm = async () => {
if (!currentDraft) return
await api.posts.create(currentDraft.formData)
clearDraft()
}
return (
<form onSubmit={(e) => { e.preventDefault(); submitForm() }}>
<input
value={currentDraft?.formData.title || ''}
onChange={(e) => updateDraft('title', e.target.value)}
/>
<button type="submit">Publish</button>
<button type="button" onClick={clearDraft}>Clear Draft</button>
</form>
)
}
사용 사례
LocalOnly 컬렉션은 다음에 적합합니다:
- 임시 UI 상태(모달, 사이드바, 툴팁)
- 현재 세션 동안의 양식 초안 데이터
- 클라이언트 측 계산 또는 파생 데이터
- 마법사/다단계 양식 상태
- 임시 필터 또는 검색 상태
- 메모리 내 캐시
LocalStorageCollection과의 비교
| 기능 | LocalOnly | LocalStorage |
|---|---|---|
| 영속성 | 없음(메모리 내에서만) | localStorage |
| 탭 간 동기화 | 아니요 | 예 |
| 페이지 새로고침 후 유지 | 아니요 | 예 |
| 성능 | 가장 빠름 | 빠름 |
| 크기 제한 | 메모리 제한 | 약 5~10MB |
| 적합한 용도 | 임시 UI 상태 | 사용자 기본 설정 |