Grid Filtering with Signals
This guide shows how to implement reactive client-side filtering for a data grid using signals. The grid automatically updates whenever users change any filter criteria - category, search term, or stock status, without manual coordination between components.
The Use Case
Data grids often need multiple filters that work together. For example, a product grid where users can filter by category, search by name or ID, and toggle to show only in-stock items. Each filter should immediately update the visible results, and all filters should work together seamlessly.
Signals simplify this by making the filtered dataset reactive. When a user changes any filter, a computed signal automatically recalculates the filtered results and updates the grid.
How It Works
Signals for Filter State
Create ValueSignal instances for each filter input:
Source code
Java
ValueSignal<String> categoryFilterSignal = new ValueSignal<>("All");
ValueSignal<String> searchTermSignal = new ValueSignal<>("");
ValueSignal<Boolean> inStockOnlySignal = new ValueSignal<>(false);These signals track the current state of each filter. When any filter changes, its signal updates automatically, triggering dependent computations.
Two-Way Binding with Filter Components
Connect signals to filter components using bindValue():
Source code
Java
categoryFilter.bindValue(categoryFilterSignal, categoryFilterSignal::set);
searchField.bindValue(searchTermSignal, searchTermSignal::set);
inStockCheckbox.bindValue(inStockOnlySignal, inStockOnlySignal::set);This creates bidirectional connections:
-
When users interact with the components, the write callback (
signal::set) updates the signal -
When code updates the signals, the framework reads via
signal.get()and updates the components
See Two-Way Signal Mapping for more details about bidirectional bindings.
Computed Signal for Filtered Data
Create a computed signal that derives the filtered dataset from all filter signals:
Source code
Java
Signal<List<Product>> filteredProductsSignal = Signal.computed(() -> {
String category = categoryFilterSignal.get();
String searchTerm = searchTermSignal.get().toLowerCase();
boolean inStockOnly = inStockOnlySignal.get();
return allProducts.stream()
.filter(p -> category.equals("All")
|| p.category().equals(category))
.filter(p -> searchTerm.isEmpty()
|| p.name().toLowerCase().contains(searchTerm)
|| p.id().toLowerCase().contains(searchTerm))
.filter(p -> !inStockOnly || p.stock() > 0)
.toList();
});This computed signal automatically recalculates whenever any of the three filter signals change. The filtering logic is centralized in one place, making it easy to understand and maintain. The signal reads all three filter values using get() and applies them sequentially using stream operations.
Binding the Grid to Filtered Data
Connect the grid to the computed signal using ComponentEffect.effect():
Source code
Java
Grid<Product> productGrid = new Grid<>(Product.class);
productGrid.setColumns("id", "name", "category", "price", "stock");
ComponentEffect.effect(productGrid, () -> {
productGrid.setItems(Objects.requireNonNullElseGet(
filteredProductsSignal.get(), List::of));
});The ComponentEffect.effect() method creates a component effect that runs whenever any signal accessed within it changes. The effect automatically tracks that it reads filteredProductsSignal.get() and reruns whenever the filtered products change. This establishes a reactive connection—whenever the computed signal recalculates (because any filter changed), the effect runs and updates the grid with the new filtered results.
Key Patterns
- Signals for filter state
-
Use
ValueSignalto track each independent filter criterion. This makes the filter state explicit and reactive. - Two-way binding with
bindValue() -
Connect filter components to signals using the pattern
component.bindValue(signal, signal::set). The first parameter is the signal (read usingsignal.get()), and the second is the write callback that is supposed to update the signal when the user changes the component, this callback can have a conditional logic that write the value if it’s valid, or does nothing otherwise. - Computed signals for derived data
-
Create a computed signal that reads all filter signals using
signal.get()and produces the filtered dataset. This centralizes filtering logic and makes dependencies clear. - Component effects for grid updates
-
Use
ComponentEffect.effect()to connect the grid to the filtered data signal. The effect automatically tracks signal access viasignal.get()and reruns whenever those signals change, updating the grid’s items.
When to Use This Pattern
This pattern is ideal when:
-
You have multiple filters that need to work together
-
Filtering logic is moderately complex or involves multiple conditions
-
You want client-side filtering for responsive interaction
-
The dataset is small enough to filter in memory (typically under 10,000 items)
For larger datasets, consider server-side filtering with lazy loading. For single, simple filters, a direct event listener approach might be sufficient.
Related Topics
-
Local Signals - Understanding ValueSignal and two-way binding
-
Effects and Computed Signals - Creating derived values
-
Component Bindings - Binding signals to component properties
-
Grid - Reference documentation for Grid component