Filtering a list
A two-way bound search box drives a Computed slice, and BindList renders whatever it contains. The list never knows a filter exists — it just renders the slice it's given.
5 of 5 posts
How it works
State
posts := []post{
{ID: "1", Title: "Signals track their own dependencies"},
{ID: "2", Title: "Keyed lists move nodes instead of rebuilding them"},
{ID: "3", Title: "A binding owns exactly the property it writes"},
{ID: "4", Title: "Computed values chain and gate"},
{ID: "5", Title: "The escape hatch is plain Go"},
}
query := reactive.NewSignal("")
reactive.BindValue(root.Find("[data-query]"), query)The posts are a plain slice — no signals, nothing reactive about them. The only state is the search box: BindValue is two-way, so typing writes query and setting the signal would write the field.
The filter
matches := reactive.Computed(func() []post {
q := strings.ToLower(strings.TrimSpace(query.Get()))
if q == "" {
return posts
}
var out []post
for _, p := range posts {
if strings.Contains(strings.ToLower(p.Title), q) {
out = append(out, p)
}
}
return out
})Computed derives the matching slice from query — it re-runs on every keystroke because it reads query.Get(). An empty query returns everything, so the filter is off by default.
The list and count
reactive.BindText(root.Find("[data-count]"), func() string {
return strconv.Itoa(len(matches.Get()))
})
reactive.BindList(root.Find("[data-results]"), matches.Get,
func(p post) string { return p.ID },
func(p post) wasmwrap.Element {
return wasmwrap.Create("li").SetText(p.Title)
})BindList renders the filtered slice with the same keyed patching as the keyed list; the count is a text binding over the same slice.
Full source
posts := []post{
{ID: "1", Title: "Signals track their own dependencies"},
{ID: "2", Title: "Keyed lists move nodes instead of rebuilding them"},
{ID: "3", Title: "A binding owns exactly the property it writes"},
{ID: "4", Title: "Computed values chain and gate"},
{ID: "5", Title: "The escape hatch is plain Go"},
}
query := reactive.NewSignal("")
reactive.BindValue(root.Find("[data-query]"), query)
matches := reactive.Computed(func() []post {
q := strings.ToLower(strings.TrimSpace(query.Get()))
if q == "" {
return posts
}
var out []post
for _, p := range posts {
if strings.Contains(strings.ToLower(p.Title), q) {
out = append(out, p)
}
}
return out
})
reactive.BindText(root.Find("[data-count]"), func() string {
return strconv.Itoa(len(matches.Get()))
})
reactive.BindList(root.Find("[data-results]"), matches.Get,
func(p post) string { return p.ID },
func(p post) wasmwrap.Element {
return wasmwrap.Create("li").SetText(p.Title)
})How this demo runs
scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the results stay put.