# Fine-grained reactivity

Drag one component on a canvas at 60fps while only that component re-renders — a folded per-frame write plus a per-row `useFragment` subscription, rooted in one live coverage query.

Dragging an object on a canvas fires a write every frame. The trick to keeping it smooth is *where*
each subscription lives: one root query syncs the whole subtree, but each item subscribes to its
**own row** with `useFragment` — so a write to one row re-renders one component, not the list.

## In the wild — Strut

On [Strut](https://github.com/tantaman/strut)'s slide canvas, one query syncs the
`deck → slides → components` tree, declared with nested fragments:

```ts
// shared/fragments.ts — the slide's components, as z-ordered masked refs
export const SlideFragment = defineFragment(slide, (f) =>
  f.sub('components', rels.slideComponents, ComponentFragment, (t) =>
    t.orderBy('z_order', 'asc'),
  ),
)
```

*[`shared/fragments.ts` L43–47](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/shared/fragments.ts#L43-L47) · Strut* — rooted once with `useRoot(deckDetailQuery, { deckId })` ([`deck.$deckId.tsx` L73](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/src/routes/deck.%24deckId.tsx#L73)).

Each component renders through its **own** `useFragment` — a masked, per-row subscription. When one
component's row changes, only this reader's `data` changes:

```tsx
// src/editor/componentFragments.tsx — one subscription per component
export function ComponentDataReader({ component, onData, onRemove, children }: ComponentDataReaderProps) {
  const data = useFragment(ComponentFragment, component.ref)
  if (!data) return null
  return (
    <ComponentData data={data} onData={onData} onRemove={onRemove}>
      {children}
    </ComponentData>
  )
}
```

*[`src/editor/componentFragments.tsx` L29–47](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/src/editor/componentFragments.tsx#L29-L47) · Strut*

The pointer-move loop writes the new position through `moveComponent.folded` on every frame (see
[folded mutations](/docs/folded-mutations)), keyed per component so the burst coalesces to the
server, then commits one undoable step on pointer-up:

```tsx
// src/editor/Stage.tsx — inside the pointermove handler
for (const frame of lastFrames) {
  mutate.moveComponent.folded(
    { key: frame.id },
    { id: frame.id, x: frame.x, y: frame.y },
  )
}
```

*[`src/editor/Stage.tsx` L706–711](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/src/editor/Stage.tsx#L706-L711) · Strut* — the mutator is a plain patch: `yield tx.update('component', { id: a.id, x: a.x, y: a.y })` ([`shared/app-def.ts` L738–743](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/shared/app-def.ts#L738-L743)).

That split — **coverage query at the root, `useFragment` at each leaf** — is what makes a drag update
the dragged object live without re-rendering its siblings.

## See also

- [Folded mutations](/docs/folded-mutations) — the per-frame write side of the same interaction.
- [Compose the UI with fragments](/docs/fragments) — declaring per-component data and rooting it once.

---

[View this page on Rindle](https://rindle.sh/docs/fine-grained-reactivity)
