Stores

There are two ways to share state:

  1. Parent state is visible to nested components automatically. See Nested components and scope. This covers most sharing inside one part of a page.
  2. A store holds app-wide state that several unrelated components use, or that should outlive any one component.

defineStore

// /state/cart.js
import { defineStore } from "@realmorg/realm";

export const cart = defineStore(
  "cart",
  {
    items: [],
    get count() {
      return this.items.reduce((sum, item) => sum + item.quantity, 0);
    },
  },
  { persist: "local" },
);
<custom-element name="cart-badge">
  <script type="module">
    import { cart } from "/state/cart.js";
  </script>

  <ui-badge><slot name="#cart.count">0</slot></ui-badge>
</custom-element>

<custom-element name="add-to-cart">
  <script type="module">
    import { cart } from "/state/cart.js";

    const props = defineProps({ sku: prop.string().required() });

    function add() {
      const line = cart.items.find((item) => item.sku === props.sku);
      if (line) line.quantity++;
      else cart.items.push({ sku: props.sku, quantity: 1 });
    }
  </script>

  <ui-button onclick="add()">Add to cart</ui-button>
</custom-element>

defineStore is also available inside component scripts without importing, which is handy for small apps:

Previewindex.htmltheme-picker.htmlaccent-preview.html
Accent:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Stores 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="./theme-picker.html"></import-element>
  <import-element from="./accent-preview.html"></import-element>

  <theme-picker></theme-picker>
  <accent-preview></accent-preview>
</body>
</html>
<custom-element name="theme-picker">
  <script>
    const settings = defineStore("docs-demo-settings", { accent: "chart-1" }, { persist: "session" });
    const ACCENTS = ["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"];
    const picker = useState({
      get swatches() {
        return ACCENTS.map((id) => ({ id, color: `var(--${id})`, current: id === settings.accent }));
      },
    });

    function choose(swatch) {
      settings.accent = swatch.id;
    }
  </script>

  <style>
    :host {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    button {
      width: calc(var(--spacing) * 8);
      height: calc(var(--spacing) * 8);
      border: 2px solid transparent;
      border-radius: var(--radius-full);
      background: var(--swatch);
      cursor: pointer;
    }
    button[aria-pressed="true"] {
      border-color: var(--foreground);
    }
  </style>

  <button type="button" repeat="#picker.swatches" as="swatch" key="id" _aria-label="#swatch.id" _aria-pressed="#swatch.current" _--swatch="#swatch.color" onclick="choose(swatch)"></button>
</custom-element>
<custom-element name="accent-preview">
  <script>
    const settings = defineStore("docs-demo-settings", { accent: "chart-1" }, { persist: "session" });
    const preview = useState({
      get color() {
        return `var(--${settings.accent})`;
      },
    });
  </script>

  <style>
    :host {
      display: block;
      padding: calc(var(--spacing) * 3) calc(var(--spacing) * 4);
      border-radius: var(--radius-md);
      background: var(--state-preview-color);
      color: var(--primary-foreground);
      font-weight: var(--font-weight-medium);
    }
  </style>

  Accent: <slot name="#settings.accent"></slot>
</custom-element>

Both components call defineStore with the same name, so they share one store. The choice survives a reload in this tab because the store persists to sessionStorage.

Persistence

persist Where values are saved
"local" localStorage, kept across sessions
"session" sessionStorage, kept for the tab
An adapter Anything with get(key) and set(key, value)

A custom adapter, for example to namespace keys or add expiry:

const adapter = {
  get: (key) => localStorage.getItem(`myapp:${key}`),
  set: (key, value) => localStorage.setItem(`myapp:${key}`, value),
};

export const prefs = defineStore("prefs", { density: "comfortable" }, { persist: adapter });

Things to know

Edit this page on GitHub · View as Markdown