Render once
Use a request-scoped handle when a component can appear many times but its markup should render only once per request.
Copy the helper
Copy this into the package that owns the singleton component:
package app
import (
"context"
"net/http"
"sync"
"sync/atomic"
)
type OnceHandle struct{ id int64 }
var onceIndex atomic.Int64
func NewOnce() *OnceHandle {
return &OnceHandle{id: onceIndex.Add(1)}
}
type onceState struct {
mu sync.Mutex
seen map[*OnceHandle]struct{}
}
type onceKeyT struct{}
func (h *OnceHandle) firstRender(ctx context.Context) bool {
state, _ := ctx.Value(onceKeyT{}).(*onceState)
if state == nil {
return true
}
state.mu.Lock()
defer state.mu.Unlock()
if _, seen := state.seen[h]; seen {
return false
}
state.seen[h] = struct{}{}
return true
}
func withOnceScope(ctx context.Context) context.Context {
if state, _ := ctx.Value(onceKeyT{}).(*onceState); state != nil {
return ctx
}
return context.WithValue(ctx, onceKeyT{}, &onceState{
seen: make(map[*OnceHandle]struct{}),
})
}
func OnceScopeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := withOnceScope(r.Context())
next.ServeHTTP(w, r.WithContext(ctx))
})
}Keep the non-zero-size id field. Go may give zero-size values the same address, which would let distinct pointer-keyed handles collide.
Install the request scope
Wrap the handler or router before rendering a page:
handler := OnceScopeMiddleware(mux)The middleware creates a fresh seen-set for each request. It is idempotent, so wrapping an already-scoped handler reuses the current set.
Add the component
Guard the children with firstRender:
component Once(handle *OnceHandle) {
{ if handle.firstRender(ctx) { { children } } }
}Use it
Declare one package-level handle for each singleton:
var dialogContainerOnce = NewOnce()
component DialogContainer() {
<Once handle={dialogContainerOnce}>
<div id="dialog-root"></div>
</Once>
}Every <DialogContainer/> call in one request shares that handle. A different handle renders its own singleton, and the same handle renders again on the next request.
What happens without middleware
Without OnceScopeMiddleware, firstRender returns true every time. Content renders on every call, but nothing panics.