{"id":20936333,"url":"https://github.com/extrange/realtime-todo-list","last_synced_at":"2025-12-25T20:57:36.733Z","repository":{"id":166348668,"uuid":"637423833","full_name":"extrange/realtime-todo-list","owner":"extrange","description":"Manage tasks within multiple lists, with realtime + offline synchronization, and collaborative editing.","archived":false,"fork":false,"pushed_at":"2024-04-07T08:01:34.000Z","size":39596,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-04-14T17:40:12.090Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tasks.nicholaslyz.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extrange.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-05-07T14:13:31.000Z","updated_at":"2024-04-22T11:27:56.407Z","dependencies_parsed_at":"2023-11-07T05:08:08.302Z","dependency_job_id":"e02bff52-f67e-4e63-9342-08f84629e366","html_url":"https://github.com/extrange/realtime-todo-list","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrange%2Frealtime-todo-list","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrange%2Frealtime-todo-list/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrange%2Frealtime-todo-list/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrange%2Frealtime-todo-list/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extrange","download_url":"https://codeload.github.com/extrange/realtime-todo-list/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243330259,"owners_count":20274037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T22:19:07.033Z","updated_at":"2025-12-25T20:57:36.692Z","avatar_url":"https://github.com/extrange.png","language":"TypeScript","readme":"# Lists App\n\nAn app to manage tasks within multiple lists, with realtime + offline synchronization, and collaborative editing.\n\n## Target Performance Metrics\n\n- TodoView total render time: \u003c 500ms\n- Editor total render time: \u003c 500ms\n- Room switch time: \u003c 500ms\n\n## Known Issues\n\n### `useSyncedStore`\n\n- `useSyncedStore` just adds a listener on any valid proxied object. This means that property access either inside `useSyncedStore` or outside work similarly (e.g. `useSyncedStore(store.todos[0])` vs `useSyncedStore(store).todos[0]`)\n  - `Editor.tsx` is an exception here\n- If a value is conditionally accessed, and during a render it is not accessed, it stops being updated for future renders.\n- Accessing an array (e.g. `state.todos`) can result in either a shallow or deep listener depending on what properties were accessed\n  - In `state.todos.length`, it is a shallow listener\n  - In `state.todos.map(t =\u003e t.focus)`, it is a deep listener, but only on the `todo.focus` property\n\nNotes:\n\n- It appears to work like a hook, in that it remembers where a property accessor was used and triggers a rerender on that (via YJS event listeners)\n- For better performance, I may want to use YJS event listeners if I only need say, additions/deletions in a list (not nested updates).\n- To prevent the whole component rerendering for a small change in item,refactor components such that each component is only listening to the dependencies it needs.\n- Memoize child components to prevent them re-rendering when the parent re-renders.\n- Properties with frequent updates - use `useDeferredValue`/`useTransition` (but only if values are memoizable - see below)\n- Simple properties e.g. `boolean`, `number` can be used as memoization dependencies.\n  - [ ] Can they be deferred?\n- If you pass a proxied value to a child, `useSyncedStore` is necessary to [set the event listener on the child][use-reactive]. For example, the following will not update to changes in `todo.focus`:\n\n  ```ts\n  const MyComponent = ({ todo }: { todo: Todo }) =\u003e {\n    return todo.focus ? \"focus\" : \"not focus\";\n  };\n  ```\n\n  Instead, do:\n\n  ```ts\n  const MyComponent = ({ todo: _todo }: { todo: Todo }) =\u003e {\n    const todo = useSyncedStore(_todo);\n    return todo.focus ? \"focus\" : \"not focus\";\n  };\n  ```\n\n  - For complex properties e.g. `todo.content`, merely accessing the value will not trigger a re-render - it needs to be manually subscribed to with `observeDeep`.\n  - In `\u003cStrictMode\u003e`, this will cause a double-render (once on the component mount, again within `useSyncedStore` as it calls `forceUpdate` internally when attaching the observer). It will not affect production.\n\n### Tooltip\n\nTooltip causes `React.memo` to fail presumably because it uses `React.cloneElement` [on its children][tooltip-clone-element].\n\n### Performance\n\nBenchmark (for 10,000 objects)\n\n- `Array.forEach` (syncedStore proxy) - 100ms\n- `getYjsValue().toArray().forEach` (raw Yjs) - 50ms\n- `Object.entries(YMap).forEach` - 200ms\n- `[...getYjsValue(YMap).entries()].forEach` - 50ms\n\nOthers:\n\n- `Array.find` (within SyncedStore proxied objects) - around 20ms for 1000 objects\n\n### React Profiler\n\n- When using `\u003cProfiler\u003e` in a mapped array, you must also give it a `key` otherwise children will re-render whenever their parent does.\n- The Profiler does not include time spent in callbacks (e.g. in ListView)\n\n[use-reactive]: https://github.com/YousefED/reactive/blob/main/packages/reactive-react/src/useReactive.ts\n[tooltip-clone-element]: https://github.com/mantinedev/mantine/blob/cf0f85faec56615ea5fbd7813e83bac60dbaefb7/src/mantine-core/src/Tooltip/Tooltip.tsx#L193\n[yjs]: https://github.com/yjs/yjs\n[hocuspocus]: https://tiptap.dev/hocuspocus\n[syncedstore]: https://syncedstore.org/docs/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextrange%2Frealtime-todo-list","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextrange%2Frealtime-todo-list","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextrange%2Frealtime-todo-list/lists"}