State
State is a value you read by calling it. When it changes, only the code that read it runs again.
Show the code
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
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.