-
toggleTodo(todo)} />
)}
Status: {issue.status}
3. **Update Metro config**
Add the following code to your `metro.config.js` file:
## Define your schema
Create a file named `schema.ts` inside the `src/livestore` folder. This file defines your LiveStore schema consisting of your app's event definitions (describing how data changes), derived state (i.e. SQLite tables), and materializers (how state is derived from events).
Here's an example schema:
## `getting-started/expo/livestore/schema.ts`
```ts filename="getting-started/expo/livestore/schema.ts"
export const tables = {
todos: State.SQLite.table({
name: 'todos',
columns: {
id: State.SQLite.text({ primaryKey: true }),
text: State.SQLite.text({ default: '' }),
completed: State.SQLite.boolean({ default: false }),
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
},
}),
uiState: State.SQLite.clientDocument({
name: 'uiState',
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
}),
}
export const events = {
todoCreated: Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
}),
todoCompleted: Events.synced({
name: 'v1.TodoCompleted',
schema: Schema.Struct({ id: Schema.String }),
}),
todoUncompleted: Events.synced({
name: 'v1.TodoUncompleted',
schema: Schema.Struct({ id: Schema.String }),
}),
todoDeleted: Events.synced({
name: 'v1.TodoDeleted',
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
}),
todoClearedCompleted: Events.synced({
name: 'v1.TodoClearedCompleted',
schema: Schema.Struct({ deletedAt: Schema.Date }),
}),
uiStateSet: tables.uiState.set,
}
const materializers = State.SQLite.materializers(events, {
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
})
const state = State.SQLite.makeState({ tables, materializers })
export const schema = makeSchema({ events, state })
```
## Configure the store
Create a `store.ts` file in the `src/livestore` folder. This file configures the store adapter and exports a custom hook that components will use to access the store.
The `useStore()` hook accepts store configuration options (schema, adapter, store ID) and returns a store instance. It suspends while the store is loading, so make sure to use a `Suspense` boundary to handle the loading state.
## `getting-started/expo/livestore/store.ts`
```ts filename="getting-started/expo/livestore/store.ts"
const syncUrl = 'https://example.org/sync'
const adapter = makePersistedAdapter({
sync: { backend: makeWsSync({ url: syncUrl }) },
})
export const useAppStore = () =>
useStore({
storeId: 'expo-todomvc',
schema,
adapter,
batchUpdates,
boot: (store) => {
if (store.query(tables.todos.count()) === 0) {
store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Make coffee' }))
}
},
})
```
## Set up the store registry
To enable store management throughout your app, create a `StoreRegistry` and provide it with a `
## Define your schema
Create a file named `schema.ts` inside the `src/livestore` folder. This file defines your LiveStore schema consisting of your app's event definitions (describing how data changes), derived state (i.e. SQLite tables), and materializers (how state is derived from events).
Here's an example schema:
## `getting-started/react-web/livestore/schema.ts`
```ts filename="getting-started/react-web/livestore/schema.ts"
// You can model your state as SQLite tables (https://docs.livestore.dev/reference/state/sqlite-schema)
export const tables = {
todos: State.SQLite.table({
name: 'todos',
columns: {
id: State.SQLite.text({ primaryKey: true }),
text: State.SQLite.text({ default: '' }),
completed: State.SQLite.boolean({ default: false }),
deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
},
}),
// Client documents can be used for local-only state (e.g. form inputs)
uiState: State.SQLite.clientDocument({
name: 'uiState',
schema: Schema.Struct({ newTodoText: Schema.String, filter: Schema.Literal('all', 'active', 'completed') }),
default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
}),
}
// Events describe data changes (https://docs.livestore.dev/reference/events)
export const events = {
todoCreated: Events.synced({
name: 'v1.TodoCreated',
schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
}),
todoCompleted: Events.synced({
name: 'v1.TodoCompleted',
schema: Schema.Struct({ id: Schema.String }),
}),
todoUncompleted: Events.synced({
name: 'v1.TodoUncompleted',
schema: Schema.Struct({ id: Schema.String }),
}),
todoDeleted: Events.synced({
name: 'v1.TodoDeleted',
schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
}),
todoClearedCompleted: Events.synced({
name: 'v1.TodoClearedCompleted',
schema: Schema.Struct({ deletedAt: Schema.Date }),
}),
uiStateSet: tables.uiState.set,
}
// Materializers are used to map events to state (https://docs.livestore.dev/reference/state/materializers)
const materializers = State.SQLite.materializers(events, {
'v1.TodoCreated': ({ id, text }) => tables.todos.insert({ id, text, completed: false }),
'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
'v1.TodoClearedCompleted': ({ deletedAt }) => tables.todos.update({ deletedAt }).where({ completed: true }),
})
const state = State.SQLite.makeState({ tables, materializers })
export const schema = makeSchema({ events, state })
```
## Create the LiveStore worker
Create a file named `livestore.worker.ts` inside the `src` folder. This file will contain the LiveStore web worker. When importing this file, make sure to add the `?worker` extension to the import path to ensure that Vite treats it as a worker file.
## `getting-started/react-web/livestore.worker.ts`
```ts filename="getting-started/react-web/livestore.worker.ts"
makeWorker({ schema })
```
## Configure the store
Create a `store.ts` file in the `src` folder. This file configures the store adapter and exports a custom hook that components will use to access the store.
The `useStore()` hook accepts store configuration options (schema, adapter, store ID) and returns a store instance. It suspends while the store is loading, so make sure to use a `Suspense` boundary to handle the loading state.
## `getting-started/react-web/store.ts`
```ts filename="getting-started/react-web/store.ts"
const adapter = makePersistedAdapter({
storage: { type: 'opfs' },
worker: LiveStoreWorker,
sharedWorker: LiveStoreSharedWorker,
})
export const useAppStore = () =>
useStore({
storeId: 'app-root',
schema,
adapter,
batchUpdates,
})
```
## Set up the store registry
To enable store management throughout your app, create a `StoreRegistry` and provide it with a `| Dark PNG | Dark SVG | Light PNG | Light SVG |
|---|---|---|---|
|
|
|
|
No todos yet. Add one above!
)}
No todos yet. Add one above!
)}No todos yet. Add one above!
)}No todos yet. Add one above!
)}
Created by: {workspace.createdByUsername}
Store ID: {workspaceStore.storeId}
No workspaces yet
) : ( workspaces.map((w) => (