> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfield.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Reactive UI updates

> Make a write update the screen instantly through a change-driven rebuild of the prepared value.

<div className="bf-article">
  <p className="bf-lead">
    When a user does something — connects a service, completes a task, sends a message — the screen should reflect it instantly, with no refresh button, no reopening the screen, and no hand-written copy of the new state. This page shows the one loop that makes that happen.
  </p>

  ## The product moment

  A user clicks **Connect** on a service tile. The credential is saved. The tile should flip to **Connected** the moment the save lands — on its own.

  The wrong way to get there is to fake it: flip the tile in component state because you "know" the connect succeeded, or re-pull the whole feed every time the screen mounts. Both drift from the real data, and both are extra code you later have to delete. The right way is a single reactive loop where the rendered screen is always a direct projection of the saved data.

  ## The loop

  <div className="bf-flow" aria-label="Reactive update loop">
    <div className="bf-flow-node">
      <span className="bf-flow-step">act</span>
      <strong>User action</strong>
      <p>A surface asks a target to do work with <code>sendRequestToBitfieldTarget(...)</code>.</p>
    </div>

    <div className="bf-flow-arrow" aria-hidden="true">→</div>

    <div className="bf-flow-node">
      <span className="bf-flow-step">change</span>
      <strong>Records change</strong>
      <p>The target writes the package records the prepared value is built from.</p>
    </div>

    <div className="bf-flow-arrow" aria-hidden="true">→</div>

    <div className="bf-flow-node">
      <span className="bf-flow-step">rebuild</span>
      <strong>Reactive rule rebuilds</strong>
      <p>A change-driven rule re-prepares the named value from the new records.</p>
    </div>

    <div className="bf-flow-arrow" aria-hidden="true">→</div>

    <div className="bf-flow-node">
      <span className="bf-flow-step">render</span>
      <strong>Component re-renders</strong>
      <p><code>useBitfieldData(...)</code> receives the new value and React re-renders.</p>
    </div>
  </div>

  Every arrow is automatic once the rule exists. The component does not poll, does not re-fetch on mount, and never holds a second copy of the truth.

  ## The read side is already live

  `useBitfieldData(...)` is not a one-time fetch. It is a live subscription to a named value. When that value changes, every component reading it re-renders with the new value.

  ```tsx theme={null}
  import { useBitfieldData } from '@bitfield/runtime-kit/react';

  type ConnectionRow = { id: string; name: string; connected: boolean };

  export function ConnectionList() {
    const feed = useBitfieldData<ConnectionRow[]>('service-connections');

    if (feed.loading) return <section>Loading connections.</section>;
    if (feed.error) return <section>Could not load connections.</section>;
    if (!feed.data) return <section>No connections yet.</section>;

    return (
      <section>
        {feed.data.map((row) => (
          <p key={row.id}>{row.name} — {row.connected ? 'Connected' : 'Not connected'}</p>
        ))}
      </section>
    );
  }
  ```

  You never call a "refresh" function here. If the named value changes, this list updates by itself. So the real question is not "how do I refresh the component?" It is "what makes the named value change?"

  ## The write side changes the records

  A user action asks a target to do work. That target writes the package records the named value is prepared from.

  ```tsx theme={null}
  import { sendRequestToBitfieldTarget } from '@bitfield/runtime-kit';

  async function connect(serviceId: string) {
    await sendRequestToBitfieldTarget({
      target: 'service-connections.connect',
      payload: { serviceId },
    });
  }
  ```

  This is the part that matters most: **the write must change the same records the prepared value is built from.** If your screen shows a value derived from records A, B, and C, then a connect that only writes record D will never change the screen — no matter how reactive the rest of the loop is. The write and the projection must agree on which records carry the truth.

  ## The missing link: one reactive rule

  Between "records changed" and "named value changed" sits the piece people forget. The prepared value does not rebuild itself just because some record changed somewhere. A package declares a **reactive rule** that watches the records the value depends on and re-prepares the value when any of them change.

  State the rule with three facts:

  | Part of the rule | What you provide                                              |
  | ---------------- | ------------------------------------------------------------- |
  | What to watch    | The records (or the fields) the prepared value is built from. |
  | When to fire     | On a change to any of those watched records.                  |
  | What to do       | Re-prepare the named value the surface reads.                 |

  Declare this once, as package data. After that the loop is closed: a write changes a watched record, the rule fires, the named value is rebuilt, and every subscribed component re-renders. No surface code runs the rebuild, so no surface can forget to.

  The rule lives next to the package data, not in the component. That separation is the whole point — the screen stays a dumb projection, and the reactive behavior is owned by the package that owns the data.

  ### Make the rule and the projection match

  The most common reason a correct-looking loop still shows stale state: the reactive rule watches one set of records, but the prepared value is built from a different set. The write updates records the rule does not watch, or the rule rebuilds from records the write does not touch. Keep one answer to "which records carry this truth," and point both the rule and the projection at it.

  ## Anti-patterns

  These three shortcuts all look like they work and all fight the loop. Delete them.

  **Optimistic local copy.** The component keeps its own `connectedIds` set and flips a row to connected because the request resolved. Now there are two truths — the local set and the prepared value — and they drift the first time a write partly fails or another surface changes the same data. Let the write change the records and let the rule rebuild the value. The render is the truth.

  **Re-pull on mount.** The surface rebuilds the whole feed in a mount effect "so it is fresh when the screen opens." This hides a missing reactive rule: it makes opening the screen the refresh, instead of the data change being the refresh. It also does nothing while the screen is already open. Remove the mount effect and add the rule.

  **Manual rebuild after every write.** Each action calls the rebuild itself right after writing. This works until one new caller forgets, and it couples every writer to the projection. One reactive rule replaces every one of these calls and can never be forgotten.

  ## What this prevents

  | Bug                                          | Cause it removes                                        |
  | -------------------------------------------- | ------------------------------------------------------- |
  | Tile flips, then snaps back.                 | Optimistic local copy drifting from the rebuilt value.  |
  | Screen only correct after reopening.         | A missing reactive rule masked by a mount-time re-pull. |
  | One screen updates, another does not.        | A writer that forgot its manual rebuild call.           |
  | Action succeeds but nothing visibly changes. | The write changed records the projection does not read. |

  ## Verify

  | Check           | Pass condition                                                                                                          |
  | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
  | Live read       | The component reads the named value with `useBitfieldData(...)` and renders loading, error, empty, and data states.     |
  | Write target    | The user action calls a target with `sendRequestToBitfieldTarget(...)`; it changes the records the value is built from. |
  | Reactive rule   | A package-declared rule rebuilds the named value when those records change.                                             |
  | No second truth | The component holds no optimistic copy, no mount-time re-pull, and no manual rebuild call.                              |
  | Instant update  | Doing the action updates the open screen with no refresh and no reopen.                                                 |

  ### What should happen

  ```text theme={null}
  The user does the action once.
  The records change.
  The prepared value rebuilds on its own.
  Every screen reading that value updates instantly.
  No component holds a private copy of the new state.
  ```

  ## Next

  Read [Read data in React](/runtime-kit/use-bitfield-data) for the read hook contract and selectors.

  Read [Send a request](/runtime-kit/send-request) for payloads, replies, and request errors on the write side.

  Read [React surface for package data](/runtime-kit/cookbook/react-surface-for-package-data) for a full read-plus-request surface to attach this loop to.

  Read [Package to screen](/runtime-kit/package-to-screen) for the full path from package data to a rendered surface.

  Read [Runtime Kit API](/reference/runtime-kit-api) for the exact `useBitfieldData(...)` and `sendRequestToBitfieldTarget(...)` contracts.
</div>
