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.
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.
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:
// 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 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 cover child edits, inserts, stable references, and rendering before server coverage completes.
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.