useState

Returns a deep reactive proxy of a plain object or array. Also exported from @realmorg/realm for use outside components.

useState<T extends object>(initial: T): T
const todo = useState({
  items: [],
  filter: "all",
  get remaining() {
    return this.items.filter((item) => !item.done).length;
  },
});

Parameters

Parameter Type Meaning
initial plain object or array The object to track. It is not copied: the proxy reads and writes it

Returns the proxy. Write to it directly: todo.filter = "done", todo.items.push(item).

What is tracked

Operation Tracked
Reading and writing properties yes, per property
Adding and deleting keys, in, Object.keys, for…in yes
Nested plain objects and arrays yes, wrapped lazily when read
Array index writes, length, push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin yes
includes, indexOf, lastIndexOf yes, and they find both raw and proxied members
Map, Set, Date, class instances no. They are returned unchanged

Writing the same value (Object.is) does not notify.

Getters and setters

Identity

Batching

Changes are collected and applied once per microtask, so several writes in a handler cause one update. tick() waits for that update. flush() applies it synchronously.

Example

Previewindex.htmltodo-mini.html
Add

remaining

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>useState demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>

  <import-element from="./todo-mini.html"></import-element>

  <todo-mini></todo-mini>
</body>
</html>
<custom-element name="todo-mini">
  <script>
    const todo = useState({
      draft: "",
      items: [{ id: 1, title: "Read the reference", done: true }],
      get remaining() {
        return this.items.filter((item) => !item.done).length;
      },
    });

    function add() {
      if (!todo.draft.trim()) return;
      todo.items.push({ id: Date.now(), title: todo.draft, done: false });
      todo.draft = "";
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 2);
      width: min(calc(var(--spacing) * 100), 100%);
    }
    form {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    label {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
  </style>

  <form onsubmit="add(); return false">
    <input aria-label="New task" _value="#todo.draft" oninput="todo.draft = this.value">
    <ui-button type="submit">Add</ui-button>
  </form>
  <div>
    <label repeat="#todo.items" as="item" key="id">
      <input type="checkbox" _checked="#item.done" onchange="item.done = this.checked">
      <slot name="#item.title"></slot>
    </label>
  </div>
  <p><slot name="#todo.remaining"></slot> remaining</p>
</custom-element>

Edit this page on GitHub · View as Markdown