@@ -40,11 +40,15 @@ import { schema, createStore } from "loro-mirror";
4040// Define your schema
4141const todoSchema = schema ({
4242 todos: schema .LoroList (
43- schema .LoroMap ({
44- id: schema .String (),
45- text: schema .String (),
46- completed: schema .Boolean ({ defaultValue: false }),
47- }),
43+ schema .LoroMap (
44+ {
45+ text: schema .String (),
46+ completed: schema .Boolean ({ defaultValue: false }),
47+ },
48+ { withCid: true },
49+ ),
50+ // Use `$cid` (reuses Loro container id; explained later)
51+ (t ) => t .$cid ,
4852 ),
4953});
5054
@@ -63,7 +67,6 @@ store.setState((s) => ({
6367 todos: [
6468 ... s .todos ,
6569 {
66- id: Date .now ().toString (),
6770 text: " Learn Loro Mirror" ,
6871 completed: false ,
6972 },
@@ -73,11 +76,11 @@ store.setState((s) => ({
7376// Or: draft-style updates (mutate a draft)
7477store .setState ((state ) => {
7578 state .todos .push ({
76- id: Date .now ().toString (),
7779 text: " Learn Loro Mirror" ,
7880 completed: false ,
7981 });
80- // no return needed
82+ // `$cid` is injected automatically for withCid maps
83+ // and reuses the underlying Loro container id (explained later)
8184});
8285
8386// Subscribe to state changes
@@ -132,23 +135,25 @@ import { schema } from "loro-mirror";
132135
133136type UserId = string & { __brand: " userId" };
134137const appSchema = schema ({
135- user: schema .LoroMap ({
136- id: schema .String <UserId >(),
137- name: schema .String (),
138- age: schema .Number ({ required: false }),
139- }),
138+ user: schema .LoroMap (
139+ {
140+ name: schema .String (),
141+ age: schema .Number ({ required: false }),
142+ },
143+ { withCid: true },
144+ ),
140145 tags: schema .LoroList (schema .String ()),
141146});
142147
143148// Inferred state type:
144149// type AppState = {
145- // user: { id: UserId ; name: string; age: number | undefined };
150+ // user: { $cid: string ; name: string; age: number | undefined };
146151// tags: string[];
147152// }
148153type AppState = InferType <typeof appSchema >;
149154```
150155
151- > ** Note** : If you need optional custom string types like ` { id ?: UserId } ` , you currently need to explicitly define it as ` schema.String<UserId >({ required: false }) `
156+ > ** Note** : If you need optional custom string types with generics (e.g., ` { status ?: Status } ` ), explicitly define them as ` schema.String<Status >({ required: false }) ` .
152157
153158For ` LoroMap ` with dynamic key-value pairs:
154159
@@ -206,12 +211,14 @@ const result = validateSchema(appSchema, {
206211``` ts
207212const todoSchema = schema ({
208213 todos: schema .LoroMovableList (
209- schema .LoroMap ({
210- id: schema .String (),
211- text: schema .String (),
212- completed: schema .Boolean ({ defaultValue: false }),
213- }),
214- (t ) => t .id ,
214+ schema .LoroMap (
215+ {
216+ text: schema .String (),
217+ completed: schema .Boolean ({ defaultValue: false }),
218+ },
219+ { withCid: true },
220+ ),
221+ (t ) => t .$cid , // stable id from Loro container id ($cid)
215222 ),
216223});
217224```
@@ -232,14 +239,17 @@ import { LoroDoc } from "loro-crdt";
232239import { schema } from " loro-mirror" ;
233240import { createLoroContext } from " loro-mirror-react" ;
234241
235- // Define your schema
242+ // Define your schema (use `$cid` from withCid maps)
236243const todoSchema = schema ({
237244 todos: schema .LoroList (
238- schema .LoroMap ({
239- id: schema .String ({ required: true }),
240- text: schema .String ({ required: true }),
241- completed: schema .Boolean ({ defaultValue: false }),
242- }),
245+ schema .LoroMap (
246+ {
247+ text: schema .String ({ required: true }),
248+ completed: schema .Boolean ({ defaultValue: false }),
249+ },
250+ { withCid: true },
251+ ),
252+ (t ) => t .$cid , // uses Loro container id; see "$cid" section below
243253 ),
244254});
245255
@@ -262,19 +272,19 @@ function App() {
262272// Todo list component
263273function TodoList() {
264274 const todos = useLoroSelector ((state ) => state .todos );
265- const toggleTodo = useLoroAction ((s , id : string ) => {
266- const i = s .todos .findIndex ((t ) => t .id === id );
275+ const toggleTodo = useLoroAction ((s , cid : string ) => {
276+ const i = s .todos .findIndex ((t ) => t .$cid === cid );
267277 if (i !== - 1 ) s .todos [i ].completed = ! s .todos [i ].completed ;
268278 }, []);
269279
270280 return (
271281 <ul >
272282 { todos .map ((todo ) => (
273- <li key = { todo .id } >
283+ <li key = { todo .$cid } >
274284 <input
275285 type = " checkbox"
276286 checked = { todo .completed }
277- onChange = { () => toggleTodo (todo .id ) }
287+ onChange = { () => toggleTodo (todo .$cid ) } // `$cid` is the Loro container id
278288 />
279289 <span >{ todo .text } </span >
280290 </li >
@@ -290,7 +300,6 @@ function AddTodoForm() {
290300 const addTodo = useLoroAction (
291301 (state ) => {
292302 state .todos .push ({
293- id: Date .now ().toString (),
294303 text: text .trim (),
295304 completed: false ,
296305 });
@@ -394,12 +403,14 @@ import { Mirror, schema, SyncDirection } from "loro-mirror";
394403
395404const todoSchema = schema ({
396405 todos: schema .LoroList (
397- schema .LoroMap ({
398- id: schema .String ({ required: true }),
399- text: schema .String ({ required: true }),
400- completed: schema .Boolean ({ defaultValue: false }),
401- }),
402- (t ) => t .id ,
406+ schema .LoroMap (
407+ {
408+ text: schema .String ({ required: true }),
409+ completed: schema .Boolean ({ defaultValue: false }),
410+ },
411+ { withCid: true },
412+ ),
413+ (t ) => t .$cid , // stable id from Loro container id ($cid)
403414 ),
404415});
405416
@@ -419,14 +430,19 @@ const unsubscribe = mirror.subscribe((state, { direction, tags }) => {
419430mirror .setState (
420431 (s ) => {
421432 s .todos .push ({
422- id: Date .now ().toString (),
423433 text: " Write docs" ,
424434 completed: false ,
425435 });
426436 },
427437 { tags: [" ui:add" ] },
428438);
429439
440+ ### How ` $cid ` Works
441+
442+ - Every Loro container has a stable container ID provided by Loro (e .g ., a map ’s ` container.id ` ).
443+ - When you enable ` withCid: true ` on ` schema.LoroMap(...) ` , Mirror injects a read - only ` $cid ` field into the mirrored state that equals the underlying Loro container ID .
444+ - ` $cid ` lives only in the app state and is never written back to the document . Mirror uses it for efficient diffs ; you can use it as a stable list selector : ` schema.LoroList(item, (x) => x.$cid) ` .
445+
430446// Cleanup
431447unsubscribe ();
432448mirror .dispose ();
0 commit comments