[]previous and next page

State

State is a value you read by calling it. When it changes, only the code that read it runs again.

For agentsUpdated
Show the code
Counter.tsxtsx
import { useStore } from "@shadow-js/core";

export function Counter() {
  const [count, setCount] = useStore(0);

  return (
    <div class="row">
      <button type="button" aria-label="Subtract one" onClick={() => setCount((n) => n - 1)}>
        -
      </button>
      <output>{count()}</output>
      <button type="button" aria-label="Add one" onClick={() => setCount((n) => n + 1)}>
        +
      </button>
      <button type="button" onClick={() => setCount(0)}>
        Reset
      </button>
    </div>
  );
}

useStore takes the starting value and returns a pair: a function that reads and a function that writes. Call the reader wherever you want the value, in JSX, a memo or an effect, and that place now depends on it. Call the writer with a value, or with a function of the previous value.

Objects and arrays#

Give useStore a plain object or an array and you get a store. Reading a property subscribes to that property alone, so writing tags does not touch the text that reads name.

Show the code
Profile.tsxtsx
import { useStore } from "@shadow-js/core";
import { For } from "@shadow-js/web";

export function Profile() {
  const [user, setUser] = useStore({ name: "Ada", tags: ["math"] });

  return (
    <div class="stack">
      <input
        aria-label="Name"
        value={user.name}
        onInput={(event) => setUser("name", event.currentTarget.value)}
      />
      <p>Hello, {user.name}.</p>
      <button type="button" onClick={() => setUser("tags", (tags) => [...tags, `tag ${tags.length + 1}`])}>
        Add a tag
      </button>
      <ul>
        <For each={user.tags}>{(tag) => <li>{tag}</li>}</For>
      </ul>
    </div>
  );
}

The store you read is a read-only view. Changes go through the setter, which accepts these forms:

You write It does
setUser({ name: "Grace" }) Merges the keys into the object.
setUser("name", "Grace") Sets one value, following a path of up to four steps.
setUser("tags", (tags) => [...tags, "x"]) Ends in a function of the previous value.
setUser((draft) => { draft.tags.push("x"); }) Edits a draft: assign, push, splice and delete all work.
setUser(reconcile(next)) Diffs next into the store, so unchanged items keep their identity.

When the DOM changes#

A write marks what read it as stale, and the update runs in a microtask. The end of an event handler flushes earlier, so the page is current when your handler returns. Reading right after a write always gives the new value. The mental model has the full timing.

Go deeper#

State with useStore covers every setter form, arrays, unwrap and snapshot, module-level state and the diagnostics. For the primitive underneath, read useSignal.