{}

State patterns

BESPA’s state model is intentionally tiny: every state variable is a key in the URL’s query string. The browser holds the current state, the server reads it on each request, and RedrawIfChanged decides what re-renders when a value moves.

Reading state

Every handler starts the same way:

state := wf.StateOf(r)
name := state.Get("name")             // empty string if absent
hasName := state.Has("name")
nameChanged := state.Changed("name")  // true during a partial redraw if "name" moved

state.Get returns the empty string for missing keys — this is the same shape as url.Values, which is what StateOf is internally.

Writing state

Links and form submissions targeting ?key=value merge into the page state and trigger an incremental redraw:

// Set state.foo to "bar" — the page redraws with the new value.
wf.Link("?foo=bar").Add("Set foo"),

// Clear state.foo by setting it to empty.
wf.Link("?foo=").Add("Clear foo"),

// Multiple variables in one click.
wf.Link("?foo=bar&panel=").Add("Set foo, close panel"),

Empty values delete the key. & between params lets a single click move several variables at once — useful for mutually-exclusive UI like “open modal, close panel.”

Reserved keys

Names starting with _ are reserved by the framework. The ones you’ll see most:

Naming conventions

State is a flat key/value store, but a couple of conventions keep it readable:

URL state vs. session state

State that should survive a refresh or a shared-link paste goes in the URL. State that’s per-user-but-not-shareable goes in the session. The showcase uses an in-memory session keyed by an HttpOnly cookie:

// website/shared/session.go
type Session struct {
    ID       string
    Theme    string
    Palette  string
    // ... per-session-but-not-URL data
}

func SessionOf(w http.ResponseWriter, r *http.Request) *Session {
    // Issues an HttpOnly SessionID cookie and stores Session in memory.
    ...
}

For a real app, swap the in-memory map for whatever persistence layer you already have — the API surface (one method per accessor) keeps the page code unchanged. See Sessions & auth for the integration pattern with real session libraries.

Query string vs. form body

wf.StateOf(r) reads from both the URL query string and the posted form body. When the same key appears in both — typical on a form post that has the page’s existing query string in the URL — the form body wins. That’s what you want: the user’s most recent input takes precedence over the URL that put them on the page.

State written via state.Set(...) lives only in the current request’s view of state; the response either echoes it back as new query parameters (on a redirect) or folds it into the next redraw fragment’s links. The framework handles round-tripping; you don’t write cookies or persistent stores from Set.

What survives a navigation

Each handler sees only the state encoded in the URL it was called with, plus whatever the form body added. Nothing carries over implicitly between page navigations. If you want a state variable to ride along to the next page, put it in the link:

wf.Link("/orders?filter="+state.Get("filter")).Add("All orders")

For state that should follow the user across every page — theme, locale, login — use a session (above). For state that’s just “return to where I was”, use the _back convention: pass ?_back=<url> when linking into a page, and call wf.RedirectBack(w, r) to return.

Isolated state in nested pages

When a page is embedded inside another (modal, side panel, named frame), each page has its own state namespace — the framework isolates them. A state variable named q in the modal’s URL does not collide with q on the parent page; they’re independent.

This is what makes EmbedHandler safe to point at any handler: the embedded page renders as if it were on its own URL, with its own query string. State written by the embedded page stays in the embedded page’s URL until the page asks to write back to the parent — that’s what the ^?key= action-URL prefix is for. See Basics → Nesting pages for the full model.

See also

When to redraw — how to react to state moves.

Basics → Incremental updates — the underlying protocol.