Skip to content

Instantly share code, notes, and snippets.

@matthew-gerstman
Last active January 18, 2026 18:59
Show Gist options
  • Select an option

  • Save matthew-gerstman/1ac6a1a64e5c899f9a9a3b20f7eaedf8 to your computer and use it in GitHub Desktop.

Select an option

Save matthew-gerstman/1ac6a1a64e5c899f9a9a3b20f7eaedf8 to your computer and use it in GitHub Desktop.
Mirror Hooks Optimization Proposal
# Mirror Hooks Optimization Proposal
## Overview
This document proposes a comprehensive approach to optimizing Mirror hooks to reduce unnecessary re-renders. It builds on two complementary changes:
1. **PR #3830** - Refactor `useMirrorList` to use derived atoms with shallow equality
2. **PR #3754** - Add selector hooks (`useMirrorListSelector`, `useMirrorFilteredMap`) for advanced cases
## Current State
### Problem: Unnecessary Re-renders
The original `useMirrorList` subscribes to the entire collection:
```typescript
// Old implementation
const allResources = useAtomValue(getResourceListAtom(mirror.getKey(), resourceType))
const filteredResults = useMemo(() => {
return allResources.filter(filterFn)
}, [allResources, filterFn])
```
**Issue:** When ANY item in the collection changes, the component re-renders—even if the filtered result is identical.
## Solution Architecture
### Layer 1: Optimized `useMirrorList` (PR #3830)
For the common case of simple filtering that returns `T[]`:
```typescript
// New implementation uses selectAtom with shallow equality
const filteredAtom = useMemo(() => {
return selectAtom(
baseAtom,
(items) => filterFn ? items.filter(filterFn) : items,
shallowArrayEqual // Only re-render if filtered result actually changed
)
}, [mirror, resourceType, depsKey])
return useAtomValue(filteredAtom)
```
**Benefit:** Components only re-render when their filtered results actually change.
### Layer 2: Selector Hooks (PR #3754)
For advanced cases requiring transformations or derived values:
| Hook | Use Case | Return Type |
|------|----------|-------------|
| `useMirrorListSelector<T, R>` | Derive any value from list | `R` |
| `useMirrorResourceSelector<T, R>` | Derive value from single resource | `R` |
| `useMirrorFilteredMap<T, R>` | Filter + transform | `R[]` |
## Coverage Analysis
| Scenario | Solution | Status |
|----------|----------|--------|
| Simple filtering → `T[]` | `useMirrorList` | ✅ PR #3830 |
| Derived values → `R` | `useMirrorListSelector` | ✅ PR #3754 |
| Filter + Map → `R[]` | `useMirrorFilteredMap` | ✅ PR #3754 |
| Single resource field | `useMirrorResourceSelector` | ✅ PR #3754 |
| Global list filtering | `useGlobalMirrorList` | ❌ Not optimized |
---
## Missing Optimizations with Real-World Examples
### 1. `useGlobalMirrorList` (High Priority) - 62 usages
Same problem as old `useMirrorList`—needs derived atom fix.
**Real examples from codebase:**
```typescript
// use-workspaces.ts - re-renders on ANY workspace change
const workspaces = useGlobalMirrorList<Workspace>('workspaces')
// use-teams.ts - re-renders on ANY team change in workspace
const allTeams = useGlobalMirrorList<Team>('teams',
(team) => team.workspaceId === workspaceId, [workspaceId])
// use-project-members.ts - re-renders on ANY grant change
const projectGrants = useGlobalMirrorList<any>('resource-access-grants',
projectGrantsFilter, [projectId])
const projectInvitations = useGlobalMirrorList<any>('resource-invitations',
projectInvitationsFilter, [projectId])
const relevantUsers = useGlobalMirrorList<any>('users')
const allWorkspaces = useGlobalMirrorList<any>('workspaces')
const allTeams = useGlobalMirrorList<any>('teams')
// use-team-project-access.ts - re-renders on ANY grant change
const grants = useGlobalMirrorList<ResourceAccessGrant>('resource-access-grants')
// use-projects.ts - re-renders on ANY project change
const allProjects = useGlobalMirrorList<Project>('projects')
const resourceGrants = useGlobalMirrorList<ResourceAccessGrant>('resource-access-grants')
```
**Fix:** Apply same `selectAtom` + `shallowArrayEqual` pattern as `useMirrorList`.
---
### 2. Count Patterns (Medium Priority)
Common pattern: get list just to count items.
**Real examples from codebase:**
```typescript
// use-thread-todos.ts - filters 3x just for counts
export function useThreadTodoStats(threadId: string) {
const todos = useThreadTodos(threadId)
return useMemo(() => {
const total = todos.length
const completed = todos.filter((todo) => todo.status === 'completed').length
const inProgress = todos.filter((todo) => todo.status === 'in_progress').length
const pending = todos.filter((todo) => todo.status === 'pending').length
return { total, completed, inProgress, pending }
}, [todos])
}
// use-notification-bell.ts - filters just for count
const unreadCount = notifications.filter((n) => !n.readAt).length
```
**Proposed convenience hook:**
```typescript
// Instead of filtering then counting
const count = useMirrorCount<Todo>('thread_todos',
(t) => t.threadId === threadId && t.status === 'completed', [threadId])
```
---
### 3. Find Single Item Patterns (Medium Priority)
Common pattern: get entire list just to find one item.
**Real examples from codebase:**
```typescript
// project-page.tsx - finds active thread from all threads
const activeThread = useMemo(() =>
allThreads.find((t) => t.id === activeThreadId), [allThreads, activeThreadId])
// project-page.tsx - finds active artifact
const activeArtifact = useMemo(
() => (artifactId ? allProjectArtifacts.find((a) => a.id === artifactId) : undefined),
[artifactId, allProjectArtifacts]
)
// skill-detail-page.tsx - finds specific file
const skillMd = files.find((f) => f.filename === 'SKILL.md' && !f.folder)
```
**Proposed convenience hook:**
```typescript
// Instead of list + find
const activeThread = useMirrorFind<Thread>('threads',
(t) => t.id === activeThreadId, [activeThreadId])
```
---
### 4. Sorted Results (Medium Priority)
Sorting creates new array references, breaking shallow equality.
**Real examples from codebase:**
```typescript
// skill-detail-page.tsx - sort after filter
const allSkillFiles = useMirrorList<SkillFile>('skill-files')
const files = useMemo(() => {
return allSkillFiles
.filter((file) => file.sessionId === skillId)
.sort((a, b) => a.order - b.order)
}, [allSkillFiles, skillId])
// project-page.tsx - sort threads
const projectThreads = useMemo(
() => allThreads.filter((thread) => thread.projectId === projectId),
[allThreads, projectId]
)
// Later sorts or finds on projectThreads
```
**Proposed convenience hook:**
```typescript
// Stable sorted list with ID-based equality
const files = useMirrorSortedList<SkillFile>('skill-files',
(file) => file.sessionId === skillId,
(a, b) => a.order - b.order,
[skillId]
)
```
---
### 5. Existence Check Patterns (Low Priority)
Common pattern: check if any item matches.
**Real examples from codebase:**
```typescript
// projects-library-page.tsx - checks if project is favorited
starred: favorites.some((fav) => fav.resourceId === apiProject.id && !fav.deletedAt)
// use-team-project-access.ts - checks if project is shared
return isProjectSharedWithTeam(grants, projectId, teamId)
// which internally uses: grants.some(...)
```
**Proposed convenience hook:**
```typescript
const isFavorited = useMirrorExists<Favorite>('favorites',
(f) => f.resourceId === projectId && !f.deletedAt, [projectId])
```
---
### 6. Dependent/Chained Queries (Low Priority)
Currently awkward to express queries that depend on other queries.
**Real example from new-artifact-route.tsx:**
```typescript
// Step 1: Get workbook IDs for project
const projectWorkbookIds = useMirrorListSelector<Artifact, Set<string>>(
'artifacts',
(a) => new Set(a.filter(x => x.projectId === pid && x.type === 'workbook').map(x => x.resourceId)),
[pid],
setEqual
)
// Step 2: Filter sheets by those IDs (awkward deps serialization)
const projectSheets = useMirrorListSelector<Sheet, Sheet[]>(
'sheets',
(s) => s.filter(x => projectWorkbookIds.has(x.workbookId)),
[[...projectWorkbookIds].sort()] // Have to serialize Set for deps
)
```
---
## Implementation Roadmap
### Phase 1: Core Optimizations (Ready)
- [x] PR #3830: `useMirrorList` derived atoms
- [x] PR #3754: Selector hooks
### Phase 2: Complete Coverage
- [ ] Apply derived atom fix to `useGlobalMirrorList` (62 usages)
- [ ] Add `useMirrorCount` convenience hook
- [ ] Add `useMirrorFind` convenience hook
- [ ] Add `useMirrorExists` convenience hook
### Phase 3: Advanced Patterns (Future)
- [ ] `useMirrorSortedList` with stable equality
- [ ] Query builder for dependent/chained queries
---
## Migration Guide
### When to use each hook
```
Need filtered list of same type?
→ useMirrorList('type', filterFn, deps)
Need derived value (count, IDs, single item)?
→ useMirrorListSelector('type', selector, deps)
Need filter + transform to different shape?
→ useMirrorFilteredMap('type', filterFn, mapFn, deps)
Need specific field from single resource?
→ useMirrorResourceSelector('type', id, selector)
Need to check if any item matches? (future)
→ useMirrorExists('type', filterFn, deps)
Need count of matching items? (future)
→ useMirrorCount('type', filterFn, deps)
Need to find single matching item? (future)
→ useMirrorFind('type', filterFn, deps)
```
### Example Refactors
**Before (re-renders on any artifact change):**
```typescript
const artifacts = useMirrorList<Artifact>('artifacts')
const suggestions = useMemo(() =>
artifacts.filter(a => a.type === 'suggestion'), [artifacts])
```
**After (re-renders only when suggestions change):**
```typescript
const suggestions = useMirrorList<Artifact>('artifacts',
a => a.type === 'suggestion', [])
```
---
**Before (re-renders on any team-member change):**
```typescript
const members = useMirrorList<TeamMember>('team-members')
const myTeamIds = useMemo(() =>
members.filter(m => m.userId === id).map(m => m.teamId), [members, id])
```
**After (re-renders only when user's teams change):**
```typescript
const myTeamIds = useMirrorListSelector<TeamMember, string[]>('team-members',
m => m.filter(x => x.userId === id).map(x => x.teamId), [id])
```
---
**Before (re-renders on any todo change):**
```typescript
const todos = useThreadTodos(threadId)
const completedCount = todos.filter(t => t.status === 'completed').length
```
**After (future - re-renders only when count changes):**
```typescript
const completedCount = useMirrorCount<Todo>('thread_todos',
t => t.threadId === threadId && t.status === 'completed', [threadId])
```
---
## Summary
The combination of optimized `useMirrorList` (PR #3830) and selector hooks (PR #3754) provides a comprehensive solution for most re-render optimization needs.
**Immediate priority:** Apply derived atom fix to `useGlobalMirrorList` (62 usages across the codebase).
**Future priorities:** Add convenience hooks for common patterns (count, exists, find) to reduce boilerplate and improve DX.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment