Skip to content
Guides contents

GuidesUI & local state

Fine-grained reactivity

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

View as Markdown

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.