Skip to content

React integration for LiveStore

While LiveStore is framework agnostic, the @livestore/react package provides a first-class integration with React.

  • High performance
  • Fine-grained reactivity (using LiveStore’s signals-based reactivity system)
  • Instant, synchronous query results (without the need for useEffect and isLoading checks)
  • Supports multiple store instances
  • Transactional state transitions (via batchUpdates)
  • Also supports Expo / React Native via @livestore/adapter-expo

When using LiveStore in React, you’ll primarily interact with these fundamental components:

  • StoreRegistry - Manages store instances with automatic caching and disposal
  • <StoreRegistryProvider> - React context provider that supplies the registry to components
  • useStore() - Suspense-enabled hook for accessing store instances

Stores are cached by their storeId and automatically disposed after being unused for a configurable duration (unusedCacheTime).

import {
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
} from 'react'
import {
function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates
as
function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates
} from 'react-dom'
import {
const makeInMemoryAdapter: (options?: InMemoryAdapterOptions) => Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
} from '@livestore/adapter-web'
import {
const queryDb: {
<TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
<TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
}

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
,
class StoreRegistry

Store Registry coordinating store loading, caching, and retention

@public

StoreRegistry
,
const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
} from '@livestore/livestore'
import {
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
,
const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
} from '@livestore/react'
import {
import schema
schema
,
import tables
tables
} from './issue.schema.ts'
const
const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions
= (
issueId: string
issueId
: string) =>
storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: `issue-${
issueId: string
issueId
}`,
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any

The LiveStore schema defining tables, events, and materializers.

schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter

Adapter used for data storage and synchronization.

adapter
:
function makeInMemoryAdapter(options?: InMemoryAdapterOptions): Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
(),
})
export const
const App: () => JSX.Element
App
= () => {
const [
const storeRegistry: StoreRegistry
storeRegistry
] =
useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, Dispatch<SetStateAction<StoreRegistry>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
(() => new
new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry

Creates a new StoreRegistry instance.

@example

const registry = new StoreRegistry({
defaultOptions: {
batchUpdates,
unusedCacheTime: 30_000,
}
})

StoreRegistry
({
defaultOptions?: Partial<Pick<RegistryStoreOptions<LiveStoreSchema.Any, {}, Codec<Json, Json, never, never>>, "batchUpdates" | "disableDevtools" | "confirmUnsavedChanges" | "debug" | "otelOptions" | "unusedCacheTime">>

Default options that are applied to all stores when they are loaded.

defaultOptions
: {
batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
} }))
return (
<
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
storeRegistry: StoreRegistry
storeRegistry
={
const storeRegistry: StoreRegistry
storeRegistry
}>
<
const IssueView: () => JSX.Element
IssueView
/>
</
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
>
)
}
const
const IssueView: () => JSX.Element
IssueView
= () => {
const
const store: Store<any, {}> & ReactApi
store
=
useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
(
const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions
('abc123'))
const [
const issue: any
issue
] =
const store: Store<any, {}> & ReactApi
store
.
useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: {
store?: Store;
}) => unknown

Returns the result of a query and subscribes to future updates.

Example:

const App = () => {
const todos = useQuery(queryDb(tables.todos.query.where({ complete: true })))
return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>
}

useQuery
(
queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly any[]>, options?: {
map?: (rows: unknown) => unknown;
label?: string;
deps?: DepKey;
} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
issue
.
any
select
()))
return <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>{
const issue: any
issue
?.
any
title
}</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
}

Create a store configuration file that exports a custom hook wrapping useStore():

import {
function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates
as
function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates
} from 'react-dom'
import {
const makeInMemoryAdapter: (options?: InMemoryAdapterOptions) => Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
} from '@livestore/adapter-web'
import {
const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
} from '@livestore/react'
import {
import schema
schema
} from './schema.ts'
const
const adapter: Adapter
adapter
=
function makeInMemoryAdapter(options?: InMemoryAdapterOptions): Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
()
export const
const useAppStore: () => Store<any, {}> & ReactApi
useAppStore
= () =>
useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: 'app-root',
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any

The LiveStore schema defining tables, events, and materializers.

schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter

Adapter used for data storage and synchronization.

adapter
,
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
,
})

The useStore() hook accepts store configuration options and returns a store instance. It suspends while the store is loading, so components using it need to be wrapped in a Suspense boundary.

Create a StoreRegistry and provide it via <StoreRegistryProvider>. Wrap in a <Suspense> to handle loading states and a <ErrorBoundary> to handle errors:

import { type
type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined

Represents all of the things React can render.

Where

ReactElement

only represents JSX, ReactNode represents everything that can be rendered.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet

@example

// Typing children
type Props = { children: ReactNode }
const Component = ({ children }: Props) => <div>{children}</div>
<Component>hello</Component>

@example

// Typing a custom element
type Props = { customElement: ReactNode }
const Component = ({ customElement }: Props) => <div>{customElement}</div>
<Component customElement={<div>hello</div>} />

ReactNode
,
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
,
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
} from 'react'
import {
function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates
as
function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
batchUpdates
} from 'react-dom'
import {
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
} from 'react-error-boundary'
import {
class StoreRegistry

Store Registry coordinating store loading, caching, and retention

@public

StoreRegistry
} from '@livestore/livestore'
import {
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
} from '@livestore/react'
const
const appErrorFallback: JSX.Element
appErrorFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Something went wrong</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
const
const appLoadingFallback: JSX.Element
appLoadingFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Loading LiveStore...</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
export const
const App: ({ children }: {
children: ReactNode;
}) => JSX.Element
App
= ({
children: ReactNode
children
}: {
children: ReactNode
children
:
type ReactNode = string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | undefined

Represents all of the things React can render.

Where

ReactElement

only represents JSX, ReactNode represents everything that can be rendered.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet

@example

// Typing children
type Props = { children: ReactNode }
const Component = ({ children }: Props) => <div>{children}</div>
<Component>hello</Component>

@example

// Typing a custom element
type Props = { customElement: ReactNode }
const Component = ({ customElement }: Props) => <div>{customElement}</div>
<Component customElement={<div>hello</div>} />

ReactNode
}) => {
const [
const storeRegistry: StoreRegistry
storeRegistry
] =
useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, Dispatch<SetStateAction<StoreRegistry>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
(() => new
new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry

Creates a new StoreRegistry instance.

@example

const registry = new StoreRegistry({
defaultOptions: {
batchUpdates,
unusedCacheTime: 30_000,
}
})

StoreRegistry
({
defaultOptions?: Partial<Pick<RegistryStoreOptions<LiveStoreSchema.Any, {}, Codec<Json, Json, never, never>>, "batchUpdates" | "disableDevtools" | "confirmUnsavedChanges" | "debug" | "otelOptions" | "unusedCacheTime">>

Default options that are applied to all stores when they are loaded.

defaultOptions
: {
batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
} }))
return (
<
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
fallback: ReactNode

Static content to render in place of an error if one is thrown.

<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />

fallback
={
const appErrorFallback: JSX.Element
appErrorFallback
}>
<
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
SuspenseProps.fallback?: ReactNode

A fallback react tree to show when a Suspense child (like React.lazy) suspends

fallback
={
const appLoadingFallback: JSX.Element
appLoadingFallback
}>
<
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
storeRegistry: StoreRegistry
storeRegistry
={
const storeRegistry: StoreRegistry
storeRegistry
}>{
children: ReactNode
children
}</
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
>
</
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
>
</
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
>
)
}

Components can now access the store via your custom hook:

import type {
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
} from 'react'
import {
function useEffect(effect: EffectCallback, deps?: DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

@parameffect Imperative function that can return a cleanup function

@paramdeps If present, effect will only activate if the values in the list change.

@version16.8.0

@seehttps://react.dev/reference/react/useEffect

useEffect
} from 'react'
import {
import events
events
} from './schema.ts'
import {
import useAppStore
useAppStore
} from './store.ts'
export const
const MyComponent: FC
MyComponent
:
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
= () => {
const
const store: any
store
=
import useAppStore
useAppStore
()
function useEffect(effect: EffectCallback, deps?: DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

@parameffect Imperative function that can return a cleanup function

@paramdeps If present, effect will only activate if the values in the list change.

@version16.8.0

@seehttps://react.dev/reference/react/useEffect

useEffect
(() => {
const store: any
store
.
any
commit
(
import events
events
.
any
todoCreated
({
id: string
id
: '1',
text: string
text
: 'Hello, world!',
createdAt: Date
createdAt
: new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
() }))
}, [
const store: any
store
])
return <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>...</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
}

Use store.useQuery() to subscribe to reactive queries:

import type {
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
} from 'react'
import {
const queryDb: {
<TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
<TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
}

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
} from '@livestore/livestore'
import {
import tables
tables
} from './schema.ts'
import {
import useAppStore
useAppStore
} from './store.ts'
const
const query$: LiveQueryDef<unknown, "def">
query$
=
queryDb<unknown, unknown>(queryInput: QueryInputRaw<unknown, readonly any[]> | QueryBuilder<unknown, any, any>, options?: {
map?: (rows: unknown) => unknown;
label?: string;
deps?: DepKey;
} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
todos
.
any
where
({
completed: boolean
completed
: true }).
any
orderBy
('id', 'desc'), {
label?: string

Used for debugging / devtools

label
: 'completedTodos',
})
export const
const CompletedTodos: FC
CompletedTodos
:
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
= () => {
const
const store: any
store
=
import useAppStore
useAppStore
()
const
const todos: any
todos
=
const store: any
store
.
any
useQuery
(
const query$: LiveQueryDef<unknown, "def">
query$
)
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
{
const todos: any
todos
.
any
map
((
todo: any
todo
) => (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
Attributes.key?: Key | null | undefined
key
={
todo: any
todo
.
any
id
}>{
todo: any
todo
.
any
text
}</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
))}
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
)
}

Use store.useClientDocument() for client-specific state:

import { type
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
,
function useCallback<T extends Function>(callback: T, deps: DependencyList): T

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
} from 'react'
import {
import tables
tables
} from './schema.ts'
import {
import useAppStore
useAppStore
} from './store.ts'
export const
const TodoItem: FC<{
id: string;
}>
TodoItem
:
type FC<P = {}> = FunctionComponent<P>

Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

@seehttps://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet

@aliasfor FunctionComponent

@example

// With props:
type Props = { name: string }
const MyComponent: FC<Props> = (props) => {
return <div>{props.name}</div>
}

@example

// Without props:
const MyComponentWithoutProps: FC = () => {
return <div>MyComponentWithoutProps</div>
}

FC
<{
id: string
id
: string }> = ({
id: string
id
}) => {
const
const store: any
store
=
import useAppStore
useAppStore
()
const [
const todo: any
todo
,
const updateTodo: any
updateTodo
] =
const store: any
store
.
any
useClientDocument
(
import tables
tables
.
any
uiState
,
id: string
id
)
const
const handleClick: () => void
handleClick
=
useCallback<() => void>(callback: () => void, deps: DependencyList): () => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(() => {
const updateTodo: any
updateTodo
({
newTodoText: string
newTodoText
: 'Hello, world!' })
}, [
const updateTodo: any
updateTodo
])
return (
<
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type
="button"
DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick
={
const handleClick: () => void
handleClick
}>
{
const todo: any
todo
.
any
newTodoText
}
</
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
>
)
}

You can have multiple stores within a single React application. This is useful for:

  • Partial data synchronization - Load only the data you need, when you need it
  • Multi-tenant applications - Separate stores for each workspace, organization, or team (like Slack workspaces or Linear teams)

Use the storeOptions() helper for type-safe, reusable configurations, and then create multiple instances for the same store configuration by using different storeId values:

import {
const makeInMemoryAdapter: (options?: InMemoryAdapterOptions) => Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
} from '@livestore/adapter-web'
import {
const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
} from '@livestore/livestore'
import {
import schema
schema
} from './issue.schema.ts'
// Define reusable store configuration with storeOptions()
// This helper provides type safety and can be reused across your app
export const
const issueStoreOptions: (issueId: string) => RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>
issueStoreOptions
= (
issueId: string
issueId
: string) =>
storeOptions<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

@paramoptions - The store configuration options

@returnsThe same options object, unchanged

@example

export const issueStoreOptions = (issueId: string) =>
storeOptions({
storeId: `issue-${issueId}`,
schema,
adapter,
unusedCacheTime: 30_000,
})
// In a component
const issueStore = useStore(issueStoreOptions(issueId))
// In a route loader or event handler
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
});

storeOptions
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: `issue-${
issueId: string
issueId
}`,
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any

The LiveStore schema defining tables, events, and materializers.

schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter

Adapter used for data storage and synchronization.

adapter
:
function makeInMemoryAdapter(options?: InMemoryAdapterOptions): Adapter

Creates a web-only in-memory LiveStore adapter.

This adapter runs entirely in memory with no persistence. Ideal for:

  • Unit tests and integration tests
  • Sandboxes and demos
  • Ephemeral sessions where persistence isn't needed

Characteristics:

  • Fast, zero I/O overhead
  • Works in all browser contexts: Window, WebWorker, SharedWorker, ServiceWorker
  • Supports optional sync backends for real-time collaboration
  • No data persists after page reload

For persistent storage, use makePersistedAdapter instead.

@example

import { makeInMemoryAdapter } from '@livestore/adapter-web'
const adapter = makeInMemoryAdapter()

@example

// With sync backend for real-time collaboration
import { makeInMemoryAdapter } from '@livestore/adapter-web'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makeInMemoryAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
})

@example

// Pre-populate with existing data
const adapter = makeInMemoryAdapter({
importSnapshot: existingDbSnapshot,
})

makeInMemoryAdapter
(),
})
import {
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
} from 'react'
import {
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
} from 'react-error-boundary'
import {
const queryDb: {
<TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
<TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
}

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
} from '@livestore/livestore'
import {
const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
} from '@livestore/react'
import {
import tables
tables
} from './issue.schema.ts'
import {
import issueStoreOptions
issueStoreOptions
} from './issue.store.ts'
const
const issueErrorFallback: JSX.Element
issueErrorFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Error loading issue</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
const
const issueLoadingFallback: JSX.Element
issueLoadingFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Loading issue...</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
export const
const IssueView: ({ issueId }: {
issueId: string;
}) => JSX.Element
IssueView
= ({
issueId: string
issueId
}: {
issueId: string
issueId
: string }) => {
// useStore() suspends the component until the store is loaded
// If the same store was already loaded, it returns immediately
const
const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore
=
useStore<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>): Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
(
import issueStoreOptions
issueStoreOptions
(
issueId: string
issueId
))
// Query data from the store
const [
const issue: any
issue
] =
const issueStore: Store<LiveStoreSchema<DbSchema, EventDefRecord>, {}> & ReactApi
issueStore
.
useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: {
store?: Store;
}) => unknown

Returns the result of a query and subscribes to future updates.

Example:

const App = () => {
const todos = useQuery(queryDb(tables.todos.query.where({ complete: true })))
return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>
}

useQuery
(
queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly any[]>, options?: {
map?: (rows: unknown) => unknown;
label?: string;
deps?: DepKey;
} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
issue
.
any
select
().
any
where
({
id: string
id
:
issueId: string
issueId
})))
if (
const issue: any
issue
== null) return <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Issue not found</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
<
JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3
>{
const issue: any
issue
.
any
title
}</
JSX.IntrinsicElements.h3: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>
h3
>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>Status: {
const issue: any
issue
.
any
status
}</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
)
}
// Wrap with Suspense and ErrorBoundary for loading and error states
export const
const IssueViewWithSuspense: ({ issueId }: {
issueId: string;
}) => JSX.Element
IssueViewWithSuspense
= ({
issueId: string
issueId
}: {
issueId: string
issueId
: string }) => {
return (
<
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
fallback: ReactNode

Static content to render in place of an error if one is thrown.

<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />

fallback
={
const issueErrorFallback: JSX.Element
issueErrorFallback
}>
<
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
SuspenseProps.fallback?: ReactNode

A fallback react tree to show when a Suspense child (like React.lazy) suspends

fallback
={
const issueLoadingFallback: JSX.Element
issueLoadingFallback
}>
<
const IssueView: ({ issueId }: {
issueId: string;
}) => JSX.Element
IssueView
issueId: string
issueId
={
issueId: string
issueId
} />
</
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
>
</
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
>
)
}

Each store instance is completely isolated with its own data, event log, and synchronization state.

When you know a store will be needed soon, preload it in advance to warm up the cache:

import {
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
,
function useCallback<T extends Function>(callback: T, deps: DependencyList): T

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
,
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
} from 'react'
import {
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
} from 'react-error-boundary'
import {
const useStoreRegistry: (override?: StoreRegistry) => StoreRegistry

Hook to access the

StoreRegistry

from context. Useful for advanced operations like preloading.

@paramoverride - Optional registry to use instead of the context value. When provided, skips context lookup entirely.

@returnsThe registry provided by the nearest StoreRegistryProvider ancestor, or the override if provided.

@throwsError if called outside a StoreRegistryProvider and no override is provided

@example

function PreloadButton({ issueId }: { issueId: string }) {
const storeRegistry = useStoreRegistry()
const handleMouseEnter = () => {
storeRegistry.preload(issueStoreOptions(issueId))
}
return <button onMouseEnter={handleMouseEnter}>View Issue</button>
}

useStoreRegistry
} from '@livestore/react'
import {
import issueStoreOptions
issueStoreOptions
} from './issue.store.ts'
import {
import IssueView
IssueView
} from './IssueView.tsx'
const
const preloadedIssueErrorFallback: JSX.Element
preloadedIssueErrorFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Error loading issue</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
const
const preloadedIssueLoadingFallback: JSX.Element
preloadedIssueLoadingFallback
= <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>Loading issue...</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
export const
const PreloadedIssue: ({ issueId }: {
issueId: string;
}) => JSX.Element
PreloadedIssue
= ({
issueId: string
issueId
}: {
issueId: string
issueId
: string }) => {
const [
const showIssue: boolean
showIssue
,
const setShowIssue: Dispatch<SetStateAction<boolean>>
setShowIssue
] =
useState<boolean>(initialState: boolean | (() => boolean)): [boolean, Dispatch<SetStateAction<boolean>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
(false)
const
const storeRegistry: StoreRegistry
storeRegistry
=
function useStoreRegistry(override?: StoreRegistry): StoreRegistry

Hook to access the

StoreRegistry

from context. Useful for advanced operations like preloading.

@paramoverride - Optional registry to use instead of the context value. When provided, skips context lookup entirely.

@returnsThe registry provided by the nearest StoreRegistryProvider ancestor, or the override if provided.

@throwsError if called outside a StoreRegistryProvider and no override is provided

@example

function PreloadButton({ issueId }: { issueId: string }) {
const storeRegistry = useStoreRegistry()
const handleMouseEnter = () => {
storeRegistry.preload(issueStoreOptions(issueId))
}
return <button onMouseEnter={handleMouseEnter}>View Issue</button>
}

useStoreRegistry
()
// Preload the store when the user hovers (before they click)
const
const handleMouseEnter: () => void
handleMouseEnter
=
useCallback<() => void>(callback: () => void, deps: DependencyList): () => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(() => {
const storeRegistry: StoreRegistry
storeRegistry
.
StoreRegistry.preload: <LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<LiveStoreSchema<DbSchema, EventDefRecord>, {}, Codec<Json, Json, never, never>>) => Promise<void>

Loads a store (without suspending) to warm up the cache.

@returnsA promise that resolves when the loading is complete (success or failure)

preload
({
...
import issueStoreOptions
issueStoreOptions
(
issueId: string
issueId
),
RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<...>>.unusedCacheTime?: number

The time in milliseconds that this store should remain in memory after becoming unused. When this store becomes unused (no active retentions), it will be disposed after this duration.

Stores transition to the unused state as soon as they have no active retentions, so when all components which use that store have unmounted.

unusedCacheTime
: 10_000, // Optionally override options
})
}, [
issueId: string
issueId
,
const storeRegistry: StoreRegistry
storeRegistry
])
const
const handleClick: () => void
handleClick
=
useCallback<() => void>(callback: () => void, deps: DependencyList): () => void

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed.

useCallback
(() => {
const setShowIssue: (value: SetStateAction<boolean>) => void
setShowIssue
(true)
}, [])
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
{
const showIssue: boolean
showIssue
== null ? (
<
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type
="button"
DOMAttributes<HTMLButtonElement>.onMouseEnter?: MouseEventHandler<HTMLButtonElement> | undefined
onMouseEnter
={
const handleMouseEnter: () => void
handleMouseEnter
}
DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick
={
const handleClick: () => void
handleClick
}>
Show Issue
</
JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
>
) : (
<
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
fallback: ReactNode

Static content to render in place of an error if one is thrown.

<ErrorBoundary fallback={<div class="text-red">Something went wrong</div>} />

fallback
={
const preloadedIssueErrorFallback: JSX.Element
preloadedIssueErrorFallback
}>
<
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
SuspenseProps.fallback?: ReactNode

A fallback react tree to show when a Suspense child (like React.lazy) suspends

fallback
={
const preloadedIssueLoadingFallback: JSX.Element
preloadedIssueLoadingFallback
}>
<
import IssueView
IssueView
issueId: string
issueId
={
issueId: string
issueId
} />
</
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
>
</
class ErrorBoundary

A reusable React error boundary component. Wrap this component around other React components to "catch" errors and render a fallback UI.

This package is built on top of React error boundaries, so it has all of the advantages and constraints of that API. This means that it can't catch errors during:

  • Server side rendering
  • Event handlers
  • Asynchronous code (including effects)

ℹ️ The component provides several ways to render a fallback: fallback, fallbackRender, and FallbackComponent. Refer to the documentation to determine which is best for your application.

ℹ️ This is a client component. You can only pass props to it that are serializeable or use it in files that have a "use client"; directive.

ErrorBoundary
>
)}
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
)
}

When creating storeId values:

  • Valid characters - Only alphanumeric characters, underscores (_), and hyphens (-) are allowed (regex: /^[a-zA-Z0-9_-]+$/)
  • Globally unique - Prefer globally unique IDs (e.g., nanoid) to prevent collisions
  • Use namespaces - Prefix with the entity type (e.g., workspace-abc123, issue-456) to avoid collisions and easier identification when debugging
  • Keep them stable - The same entity should always use the same storeId across renders
  • Sanitize user input - If incorporating user data, validate/sanitize to prevent injection attacks
  • Document your conventions - Document special IDs like user-current as they’re part of your API contract

You can customize the logger and log level for debugging:

export const
const useAppStore: () => Store<any, {}> & ReactApi
useAppStore
= () =>
useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: 'app-root',
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any

The LiveStore schema defining tables, events, and materializers.

schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter

Adapter used for data storage and synchronization.

adapter
,
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
,
// Optional: swap the logger implementation
logger?: Layer<never, never, never> | undefined

Optional Effect logger layer to control logging output.

logger
:
import Logger
Logger
.
const layer: <readonly [Logger.Logger<unknown, void>]>(loggers: readonly [Logger.Logger<unknown, void>], options?: {
readonly mergeWithExisting?: boolean | undefined;
} | undefined) => Layer<never, never, never>

Creates a Layer which will overwrite the current set of loggers with the specified array of loggers.

Details

If the specified array of loggers should be merged with the current set of loggers (instead of overwriting them), set mergeWithExisting to true.

Example (Providing logger layers)

import { Effect, Logger } from "effect"
// Single logger layer
const JsonLoggerLive = Logger.layer([Logger.consoleJson])
// Multiple loggers layer
const MultiLoggerLive = Logger.layer([
Logger.consoleJson,
Logger.consolePretty(),
Logger.formatStructured
])
// Merge with existing loggers
const AdditionalLoggerLive = Logger.layer(
[Logger.consoleJson],
{ mergeWithExisting: true }
)
// Using multiple logger formats
const jsonLogger = Logger.consoleJson
const prettyLogger = Logger.consolePretty()
const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger])
const program = Effect.log("Application started").pipe(
Effect.provide(CustomLoggerLive)
)

@since4.0.0

layer
([
import Logger
Logger
.
const consolePretty: (options?: {
readonly colors?: "auto" | boolean | undefined;
readonly stderr?: boolean | undefined;
readonly formatDate?: ((date: Date) => string) | undefined;
readonly mode?: "browser" | "tty" | "auto" | undefined;
}) => Logger.Logger<unknown, void>

A Logger which outputs logs in a "pretty" format and writes them to the console.

Details

For example, pretty output can render as [09:37:17.579] INFO (#1) label=0ms: hello followed by an annotation line such as key: value.

Example (Logging with pretty console output)

import { Effect, Logger } from "effect"
// Use the pretty console logger with default settings
const basicPretty = Effect.log("Hello Pretty Format").pipe(
Effect.provide(Logger.layer([Logger.consolePretty()]))
)
// Configure pretty logger options
const customPretty = Logger.consolePretty({
colors: true,
stderr: false,
mode: "tty",
formatDate: (date) => date.toLocaleTimeString()
})
// Perfect for development environment
const developmentProgram = Effect.gen(function*() {
yield* Effect.log("Application starting")
yield* Effect.logInfo("Database connected")
yield* Effect.logWarning("High memory usage detected")
}).pipe(
Effect.annotateLogs("environment", "development"),
Effect.withLogSpan("startup"),
Effect.provide(Logger.layer([customPretty]))
)
// Disable colors for CI/CD environments
const ciLogger = Logger.consolePretty({ colors: false })

@since4.0.0

consolePretty
()]),
// Optional: set minimum log level (use "None" to disable)
logLevel?: LogLevel | undefined

Optional minimum log level for the runtime.

logLevel
: 'Info',
})
import {
const defineMaterializer: <TEventDef extends State.SQLite.EventDef.AnyWithoutFn>(_eventDef: TEventDef, materializer: State.SQLite.Materializer<TEventDef>) => State.SQLite.Materializer<TEventDef>

Type-safe wrapper for defining a single materializer.

Useful when defining materializers separately from the materializers() builder. The first argument provides type inference for the second.

@example

const todoCreatedHandler = defineMaterializer(
events.todoCreated,
({ id, text }) => tables.todos.insert({ id, text, completed: false })
)

defineMaterializer
,
import Events
Events
,
const makeSchema: <TInputSchema extends InputSchema>(inputSchema: TInputSchema) => FromInputSchema.DeriveSchema<TInputSchema>
makeSchema
,
import Schema
Schema
,
type SessionIdSymbol = typeof SessionIdSymbol
const SessionIdSymbol: typeof SessionIdSymbol

Can be used in queries to refer to the current session id. Will be replaced with the actual session id at runtime.

In client document table:

const uiState = State.SQLite.clientDocument({
name: 'ui_state',
schema: Schema.Struct({
theme: Schema.Literals(['dark', 'light', 'system']),
user: Schema.String,
showToolbar: Schema.Boolean,
}),
default: { value: defaultFrontendState, id: SessionIdSymbol },
})

Or in a client document query:

const query$ = queryDb(tables.uiState.get(SessionIdSymbol))

SessionIdSymbol
,
import State
State
} from '@livestore/livestore'
export const
const tables: {
readonly todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>;
readonly uiState: State.SQLite.ClientDocumentTableDef<...>;
}
tables
= {
todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"todos", {
readonly id: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdAt: {
...;
};
}>, State.SQLite.WithDefaults<...>, Schema.Struct<...>>
todos
:
import State
State
.
import SQLite
SQLite
.
function table<"todos", {
readonly id: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdAt: {
...;
};
}, Partial<...>>(args: {
...;
} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)

Creates a SQLite table definition from columns or an Effect Schema.

This function supports two main ways to define a table:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const usersTable = State.SQLite.table({
name: 'users',
columns: {
id: State.SQLite.text({ primaryKey: true }),
name: State.SQLite.text({ nullable: false }),
email: State.SQLite.text({ nullable: false }),
age: State.SQLite.integer({ nullable: true }),
},
})
// Using Effect Schema with annotations
import { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({
id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement),
email: Schema.String.pipe(State.SQLite.withUnique),
name: Schema.String,
active: Schema.Boolean.pipe(State.SQLite.withDefault(true)),
createdAt: Schema.optional(Schema.Date),
})
// Option 1: With explicit name
const usersTable = State.SQLite.table({
name: 'users',
schema: UserSchema,
})
// Option 2: With name from schema annotation (title or identifier)
const AnnotatedUserSchema = UserSchema.annotate({ title: 'users' })
const usersTable2 = State.SQLite.table({
schema: AnnotatedUserSchema,
})
// Adding indexes
const PostSchema = Schema.Struct({
id: Schema.String.pipe(State.SQLite.withPrimaryKey),
title: Schema.String,
authorId: Schema.String,
createdAt: Schema.Date,
}).annotate({ identifier: 'posts' })
const postsTable = State.SQLite.table({
schema: PostSchema,
indexes: [
{ name: 'idx_posts_author', columns: ['authorId'] },
{ name: 'idx_posts_created', columns: ['createdAt'], isUnique: false },
],
})

table
({
name: "todos"
name
: 'todos',
columns: {
readonly id: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
};
readonly text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly completed: {
columnType: "integer";
schema: Schema.Codec<boolean, number, never, never>;
default: Some<false>;
nullable: false;
primaryKey: false;
autoIncrement: false;
};
readonly createdAt: {
...;
};
}
columns
: {
id: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
}
id
:
import State
State
.
import SQLite
SQLite
.
const text: <string, string, false, typeof NoDefault, true, false>(args: {
schema?: Schema.Codec<string, string, never, never>;
default?: typeof NoDefault;
nullable?: false;
primaryKey?: true;
autoIncrement?: false;
}) => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: true;
autoIncrement: false;
} (+1 overload)
text
({
primaryKey?: true
primaryKey
: true }),
text: {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
}
text
:
import State
State
.
import SQLite
SQLite
.
const text: () => {
columnType: "text";
schema: Schema.Codec<string, string, never, never>;
default: None<never>;
nullable: false;
primaryKey: false;
autoIncrement: false;
} (+1 overload)
text
(),

Use "None" to disable logging entirely.

Helper for defining reusable store options with full type inference. Returns options that can be passed to useStore() or storeRegistry.preload().

Options:

  • storeId - Unique identifier for this store instance
  • schema - The LiveStore schema
  • adapter - The platform adapter
  • unusedCacheTime? - Time in ms to keep this store in cache after it becomes unused (default: 60_000 in browser, Infinity in non-browser environments). Overrides the registry-level default when set.
  • batchUpdates? - Function for batching React updates (recommended)
  • boot? - Function called when the store is loaded
  • onBootStatus? - Callback for boot status updates
  • context? - User-defined context for dependency injection
  • syncPayload? - Payload sent to sync backend (e.g., auth tokens)
  • syncPayloadSchema? - Schema for type-safe sync payload validation
  • confirmUnsavedChanges? - Register beforeunload handler (default: true, web only)
  • logger? - Custom logger implementation
  • logLevel? - Log level (e.g., "Info", "Debug", "None")
  • otelOptions? - OpenTelemetry configuration ({ tracer, rootSpanContext })
  • disableDevtools? - Whether to disable devtools (boolean | 'auto', default: 'auto')
  • debug? - Debug options ({ instanceId?: string })

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

  • Suspends until the store is loaded.
  • Throws an error if loading fails.
  • Store gets cached by its storeId in the StoreRegistry. Multiple calls with the same storeId return the same store instance.
  • Store is cached as long as it’s being used, and after unusedCacheTime expires (default 60_000 ms in browser, Infinity in non-browser)
  • Default store options can be configured in StoreRegistry constructor.
  • Store options are only applied when the store is loaded. Subsequent calls with different options will not affect the store if it’s already loaded and cached in the registry.

store.commit(...events) / store.commit(txnFn)

Section titled “store.commit(...events) / store.commit(txnFn)”

Commits events to the store. Supports multiple calling patterns:

  • store.commit(...events) - Commit one or more events
  • store.commit(txnFn) - Commit events via a transaction function
  • store.commit(options, ...events) - Commit with options
  • store.commit(options, txnFn) - Options with transaction function

Options:

  • skipRefresh - Skip refreshing reactive queries after commit (advanced)

Subscribes to a reactive query. Re-renders the component when the result changes.

  • Takes any Queryable: QueryBuilder, LiveQueryDef, SignalDef, or LiveQuery instance
  • Returns the query result

store.useClientDocument(table, id?, options?)

Section titled “store.useClientDocument(table, id?, options?)”

React.useState-like hook for client-document tables.

  • Returns [row, setRow, id, query$] tuple
  • Works only with tables defined via State.SQLite.clientDocument()
  • If the table has a default id, the id argument is optional

React hook that subscribes to sync status changes. Re-renders the component when sync status changes.

function SyncIndicator() {
const store = useStore(storeOptions)
const status = store.useSyncStatus()
return <span>{status.isSynced ? '✓ Synced' : `Syncing (${status.pendingCount} pending)...`}</span>
}

For the SyncStatus type and non-React APIs (syncStatus(), subscribeSyncStatus(), syncStatusStream()), see the Store documentation.

Creates a registry that coordinates store loading, caching, and retention.

Config:

  • defaultOptions? - Default options that are applied to all stores when they are loaded.:
    • batchUpdates? - Function for batching React updates
    • unusedCacheTime? - Cache time for unused stores
    • disableDevtools? - Whether to disable devtools
    • confirmUnsavedChanges? - beforeunload confirmation
    • otelOptions? - OpenTelemetry configuration
    • debug? - Debug options
  • runtime? - Effect runtime for registry operations

React context provider that makes a StoreRegistry available to descendant components.

Props:

  • storeRegistry - The registry instance

Hook that returns the StoreRegistry provided by the nearest <StoreRegistryProvider> ancestor, or the override if provided.

Loads a store (without suspending) to warm up the cache. Returns a Promise that resolves when loading completes. This is a fire-and-forget operation useful for warming up the cache.

LiveStore works with Vite out of the box.

LiveStore works with Tanstack Start out of the box.

When using LiveStore with TanStack Start, place <StoreRegistryProvider> in the correct location to avoid remounting on navigation.

Use the component prop on createRootRoute for <StoreRegistryProvider>:

import { Outlet, HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { Suspense, useState } from 'react'
export const Route = createRootRoute({
shellComponent: RootShell, // HTML structure only - NO state or providers
component: RootComponent, // App shell - StoreRegistryProvider goes HERE
})
// HTML document shell - keep this stateless
function RootShell({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head><HeadContent /></head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
// App shell - persists across SPA navigation
function RootComponent() {
const [storeRegistry] = useState(() => new StoreRegistry())
return (
<Suspense fallback={<div>Loading LiveStore...</div>}>
<StoreRegistryProvider storeRegistry={storeRegistry}>
<Outlet />
</StoreRegistryProvider>
</Suspense>
)
}

TanStack Start’s shellComponent is designed for SSR HTML streaming and may be re-evaluated on server requests during navigation. When <StoreRegistryProvider> is placed there, the WebSocket connection is re-established and all LiveStore state is re-initialized on each navigation.

If you see the loading screen on every navigation, check your server logs for multiple “Launching WebSocket” messages.

LiveStore has a first-class integration with Expo / React Native via @livestore/adapter-expo. See the Expo Adapter documentation.

Given various Next.js limitations, LiveStore doesn’t yet work with Next.js out of the box.

See the Multi-Store example for a complete working application demonstrating various multi-store patterns.

  • @livestore/react uses React.useState() under the hood for useQuery() / useClientDocument() to bind LiveStore’s reactivity to React’s reactivity. Some libraries use React.useSyncExternalStore() for similar purposes but React.useState() is more efficient for LiveStore’s architecture.
  • @livestore/react supports React Strict Mode.