JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 66
  • Score
    100M100P100Q83619F
  • License MIT

React hooks and context for Loro Mirror: type-safe CRDT-backed state with selective subscriptions.

Package Exports

  • loro-mirror-react

Readme

Loro Mirror React

React integration for Loro Mirror - a state management library with Loro CRDT synchronization.

Installation

npm install loro-mirror-react loro-mirror loro-crdt
# or
yarn add loro-mirror-react loro-mirror loro-crdt
# or
pnpm add loro-mirror-react loro-mirror loro-crdt

Usage

Basic Usage with Hooks

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...
}

Using Context Provider

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 (
        <LoroProvider doc={doc} initialState={{ todos: [] }}>
            <TodoList />
            <AddTodoForm />
        </LoroProvider>
    );
}

// Todo list component
function TodoList() {
    // Subscribe only to the todos array
    const todos = useLoroSelector((state) => state.todos);

    return (
        <ul>
            {todos.map((todo) => (
                <TodoItem
                    key={todo.$cid /* stable key from Loro container id */}
                    todo={todo}
                />
            ))}
        </ul>
    );
}

// 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 (
        <li>
            <input
                type="checkbox"
                checked={todo.completed}
                onChange={toggleTodo}
            />
            <span>{todo.text}</span>
        </li>
    );
}

API Reference

useLoroStore

Creates and manages a Loro Mirror store.

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.

useLoroValue

Subscribes to a specific value from a Loro Mirror store.

const todos = useLoroValue(store, (state) => state.todos);

useLoroCallback

Creates a callback that updates a Loro Mirror store.

const addTodo = useLoroCallback(
    store,
    (state, text) => {
        state.todos.push({ text, completed: false }); // `$cid` is injected from Loro container id
    },
    [
        /* dependencies */
    ],
);

// Usage
addTodo("New todo");

createLoroContext

Creates a context provider and hooks for a Loro Mirror store.

const {
    LoroContext,
    LoroProvider,
    useLoroContext,
    useLoroState,
    useLoroSelector,
    useLoroAction,
} = createLoroContext(schema);

LoroProvider

Provider component for the Loro Mirror context.

<LoroProvider
    doc={loroDoc}
    initialState={initialState}
    validateUpdates={true}
    throwOnValidationError={false}
    debug={false}
>
    {children}
</LoroProvider>

useLoroContext

Hook to access the Loro Mirror store from context.

const store = useLoroContext();

useLoroState

Hook to access and update the full state.

const [state, setState] = useLoroState();

useLoroSelector

Hook to select a specific value from the state.

const todos = useLoroSelector((state) => state.todos);

useLoroAction

Hook to create an action that updates the state.

const addTodo = useLoroAction(
  (state, text) => {
    state.todos.push({ text, completed: false }); // `$cid` comes from Loro container id
  },
  [/* dependencies */]
);

### `$cid` and list keys/selectors

- `$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');

License

MIT