# Fine-grained reactivity

Use fragment references and per-row reads to limit React updates to the components whose data changes.

Fine-grained reactivity means that a component subscribes to the data it renders.
An edit to one comment can update that comment's component without updating the issue card.
This reduces work for large lists and editors.

This guide continues the runnable [fragment example](https://rindle.sh/docs/fragments).
It uses that example's `shared/fragments.ts`, `src/local-store.ts`, and `src/IssueCards.tsx`.
Complete that example first.

## Separate list membership from row fields

The example has three local reads:

| Component | Data it reads |
| --- | --- |
| `IssueCards` | The ordered list of issue references |
| `IssueCard` | One issue's title and ordered comment references |
| `CommentView` | One comment's ID and body |

`CommentFragment` selects `id` and `body`.
`IssueCardFragment` includes that fragment through `.sub()`.
The parent receives comment references, so a comment body edit does not change its comment list.

The root query includes all required fields for synchronization.
The child readers then open narrower local views.
They retain the same named root query, rather than opening one remote subscription per comment.

## Keep existing children stable after list changes

When a comment is inserted, the parent must render its new list.
React also renders its children by default.
Wrap `CommentView` in `memo` to skip existing children whose props remain unchanged.

In `src/IssueCards.tsx`, add the React import and replace `CommentView` with this definition.
The other imports and components stay as defined in the fragment example.

```tsx
import { memo } from "react";

export const CommentView = memo(function CommentView({ comment }: { comment: CommentRef }) {
  const data = useFragment(CommentFragment, comment);
  if (data === null) return null;
  return <li>{data.body}</li>;
});
```

`memo` only handles React renders caused by unchanged props.
It does not block the component's own fragment subscription.
An edit to `body` still updates `CommentView`.

## Try a field edit

Add this component to the same example:

```tsx
// src/CommentControls.tsx
import { useState } from "react";
import { store } from "./local-store.ts";

export function CommentControls() {
  const [error, setError] = useState<string | null>(null);

  async function changeComment() {
    setError(null);
    try {
      const oldRow = await store.readOnce(store.query.comment.where.id("c1").one());
      if (oldRow === null) return;
      await store.write((tx) => {
        tx.edit("comment", oldRow, { ...oldRow, body: "The screenshot is ready." });
      });
    } catch (cause) {
      setError(String(cause));
    }
  }

  return (
    <>
      <button onClick={() => void changeComment()}>Edit the first comment</button>
      {error && <p role="alert">{error}</p>}
    </>
  );
}
```

In `src/main.tsx`, import `CommentControls` from `./CommentControls.tsx`.
Render `<CommentControls />` beside `<IssueCards />` inside the existing provider.
The button changes the first comment's text.
React DevTools can show the components that render during that change.

This direct write is for the example's local store.
In a synced app, use a [shared mutator](https://rindle.sh/docs/mutators) for the write.
The same fragment subscription pattern applies to optimistic and confirmed changes.

## Know what still changes

| Change | Expected local read updates |
| --- | --- |
| Edit `comment.body` | That comment's fragment read |
| Edit `issue.title` | That issue card's fragment read |
| Add or remove a comment | The issue card's comment references and affected child reads |
| Change a column used to filter or order a list | The affected list and affected row reads |
| Change a React prop or context | Normal React rendering rules apply |

A fragment cannot isolate fields that it reads together.
If a parent uses `useQuery()` to read the complete nested result, child data changes can update that parent.
An inline `.sub()` builder also keeps its nested data in the parent's result.

Smaller fragments create more local views and subscriptions.
Use them where component boundaries and update frequency justify that cost.
The Rindle repository's [fragment React tests](https://github.com/tantaman/rindle/blob/main/packages/react/test/fragment-local-reads.test.ts)
cover child edits, inserts, stable references, and rendering before server coverage completes.

[Folded mutations](https://rindle.sh/docs/folded-mutations) address a different cost: the number of writes sent during repeated edits.
They can accompany fragment reads in a drag interaction or text editor.

---

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