Skip to content

Example: Shared State (Multi-User Live Counter)

app.shared_state holds ReactiveVar instances visible to all connected sessions. When one user increments the counter, every other open browser tab updates instantly.

See examples/shared_state_demo.py in the repo for the full runnable file.


shared_state_demo.py
import fluxui as ui

app = ui.App("Shared State Demo")

# ── Module-level shared state ─────────────────────────────────────────────────
# This ReactiveVar is created once when the module loads and shared by
# every session.  Changes made by any user are visible to all users.
app.shared_state["counter"] = ui.state(0)
app.shared_state["history"] = ui.list_state([])   # log of recent changes


# ── Page ─────────────────────────────────────────────────────────────────────

@app.page("/")
def home(session):
    # References to the shared ReactiveVars
    counter = app.shared_state["counter"]
    history = app.shared_state["history"]

    # Per-session state (each user has their own name)
    user_name = ui.state("Anonymous")

    def increment():
        counter.update(lambda v: v + 1)
        name = user_name.peek() or "Anonymous"
        history.value.insert(0, f"{name} added +1  →  {counter.peek()}")
        if len(history.value) > 10:
            del history.value[10:]

    def decrement():
        counter.update(lambda v: v - 1)
        name = user_name.peek() or "Anonymous"
        history.value.insert(0, f"{name} added −1  →  {counter.peek()}")
        if len(history.value) > 10:
            del history.value[10:]

    def reset():
        counter.set(0)
        history.value.insert(0, f"{user_name.peek() or 'Anonymous'} reset the counter")
        if len(history.value) > 10:
            del history.value[10:]

    with ui.Column(gap="var(--s-6)"):
        ui.Heading("Live Shared Counter", level=1)
        ui.Alert(
            "Open this page in multiple browser tabs — they all see the same counter. "
            "Every button click is immediately reflected everywhere.",
            variant="info",
        )

        # ── User name ────────────────────────────────────────────────────
        with ui.Card(title="Your name"):
            ui.TextInput(
                label="",
                placeholder="Enter your name…",
                value=user_name,
                on_change=user_name.set,
                help_text="Your name will appear in the change history below.",
            )

        # ── Shared counter ───────────────────────────────────────────────
        with ui.Card(title="Shared counter"):
            with ui.Row(justify="center", align="center", gap="var(--s-4)"):
                ui.Button("−", variant="secondary", on_click=decrement)
                ui.Metric(
                    label="Current value (shared across all sessions)",
                    value=lambda: str(counter.value),   # (1) subscribes to shared ReactiveVar
                )
                ui.Button("+", on_click=increment)

            ui.Button("Reset to 0", variant="ghost", on_click=reset)

        # ── Change history ───────────────────────────────────────────────
        with ui.Card(title="Recent changes (last 10)"):
            if history.value:
                for entry in history.value:
                    with ui.Row(gap="var(--s-2)", align="center"):
                        ui.Badge("•", variant="info")
                        ui.Text(entry, variant="caption")
            else:
                ui.Empty(
                    title="No changes yet",
                    description="Click + or − to make the first change.",
                    icon="📋",
                )


if __name__ == "__main__":
    app.run(port=8000)
  1. lambda: str(counter.value) is evaluated during the Metric's render, so each session's metric subscribes to the shared counter. When any session mutates counter, all subscribed metrics across all sessions receive _mark_dirty() and are re-rendered.

How Shared State Works

Session A (tab 1)              Session B (tab 2)
      │                               │
      │  counter.update(v+1)          │
      │                               │
      │  counter._notify()  ──────────┤
      │                               │
      │  Metric A._mark_dirty()       │  Metric B._mark_dirty()
      │  re-render → WS patch         │  re-render → WS patch
      │                               │
      ▼                               ▼
  Browser tab 1 updates          Browser tab 2 updates

The shared ReactiveVar has subscribers from every active session. When it changes, FluxUI notifies all of them and sends patches over each session's individual WebSocket.


Thread safety

app.shared_state is designed for simple shared values. For high-concurrency scenarios, protect mutations with async with app._shared_lock: to avoid race conditions:

async def safe_increment():
    async with app._shared_lock:
        counter.set(counter.peek() + 1)

Run it

pip install fluxui
python shared_state_demo.py

Open http://127.0.0.1:8000 in two browser tabs and click the buttons.