React hooks and context for Loro Mirror: type-safe CRDT-backed state with selective subscriptions.
npm install loro-mirror-reactReact integration for Loro Mirror - a state management library with Loro CRDT synchronization.
``bash`
npm install loro-mirror-react loro-mirror loro-crdtor
yarn add loro-mirror-react loro-mirror loro-crdtor
pnpm add loro-mirror-react loro-mirror loro-crdt
`tsx
import React, { useMemo } from "react";
import { LoroDoc } from "loro-crdt";
import { schema } from "loro-mirror";
import { useLoroStore } from "loro-mirror-react";
// Define your schema
const todoSchema = schema({
todos: schema.LoroList(
schema.LoroMap({
text: schema.String({ required: true }),
completed: schema.Boolean({ defaultValue: false }),
}),
// Use $cid (reuses Loro container id; explained below)
(item) => item.$cid,
),
filter: schema.String({ defaultValue: "all" }),
});
function TodoApp() {
// Create a Loro document
const doc = useMemo(() => new LoroDoc(), []);
// Create a store
const { state, setState } = useLoroStore({
doc,
schema: todoSchema,
initialState: { todos: [], filter: "all" },
});
// Add a new todo (synchronous; the update is applied before return)
const addTodo = (text: string) => {
setState((s) => ({
...s,
todos: [...s.todos, { text, completed: false }],
}));
};
// Rest of your component...
}
`
`tsx
import React, { useMemo } from "react";
import { LoroDoc } from "loro-crdt";
import { schema } from "loro-mirror";
import { createLoroContext } from "loro-mirror-react";
// Define your schema
const todoSchema = schema({
todos: schema.LoroList(
schema.LoroMap({
text: schema.String({ required: true }),
completed: schema.Boolean({ defaultValue: false }),
}),
(t) => t.$cid, // stable id from Loro container id
),
});
// Create a context
const {
LoroProvider,
useLoroContext,
useLoroState,
useLoroSelector,
useLoroAction,
} = createLoroContext(todoSchema);
// Root component
function App() {
const doc = useMemo(() => new LoroDoc(), []);
return (
);
}
// Todo list component
function TodoList() {
// Subscribe only to the todos array
const todos = useLoroSelector((state) => state.todos);
return (
// Todo item component
function TodoItem({ todo }) {
const toggleTodo = useLoroAction((state) => {
const todoIndex = state.todos.findIndex((t) => t.$cid === todo.$cid); // compare by $cid
if (todoIndex !== -1) {
state.todos[todoIndex].completed =
!state.todos[todoIndex].completed;
}
});
return (
API Reference
$3
Creates and manages a Loro Mirror store.
`tsx
const { state, setState, store } = useLoroStore({
doc,
schema,
initialState,
validateUpdates,
throwOnValidationError,
debug,
});Notes on updates:
-
setState from useLoroStore and the setter from useLoroState run synchronously; subsequent code can read the updated state immediately.
- useLoroCallback and useLoroAction return synchronous functions that call setState under the hood.
`$3
Subscribes to a specific value from a Loro Mirror store.
`tsx
const todos = useLoroValue(store, (state) => state.todos);
`$3
Creates a callback that updates a Loro Mirror store.
`tsx
const addTodo = useLoroCallback(
store,
(state, text) => {
state.todos.push({ text, completed: false }); // $cid is injected from Loro container id
},
[
/ dependencies /
],
);// Usage
addTodo("New todo");
`$3
Creates a context provider and hooks for a Loro Mirror store.
`tsx
const {
LoroContext,
LoroProvider,
useLoroContext,
useLoroState,
useLoroSelector,
useLoroAction,
} = createLoroContext(schema);
`####
LoroProviderProvider component for the Loro Mirror context.
`tsx
doc={loroDoc}
initialState={initialState}
validateUpdates={true}
throwOnValidationError={false}
debug={false}
>
{children}
`####
useLoroContextHook to access the Loro Mirror store from context.
`tsx
const store = useLoroContext();
`####
useLoroStateHook to access and update the full state.
`tsx
const [state, setState] = useLoroState();
`####
useLoroSelectorHook to select a specific value from the state.
`tsx
const todos = useLoroSelector((state) => state.todos);
`####
useLoroActionHook to create an action that updates the state.
`tsx
const addTodo = useLoroAction(
(state, text) => {
state.todos.push({ text, completed: false }); // $cid comes from Loro container id
},
[/ dependencies /]
);$3
-
$cid is always available on LoroMap state and mirrors the underlying Loro container id.
- Use $cid for React key and as the list idSelector for stable identity across edits and moves: schema.LoroList(item, x => x.$cid).// Usage
addTodo('New todo');
``MIT