Selectors & Computed State
StateMesh provides two ways to derive state: selectors (functions that extract and transform state) and computed values (cached derived state with automatic dependency tracking).
Selectors
A selector is a function that extracts a piece of state:
const cartCount = (state) => state.cart.items.reduce((sum, item) => sum + item.quantity, 0);
const cartTotal = (state) => state.cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);useMeshSelector
Use useMeshSelector in React components to subscribe to derived state:
import { useMeshSelector } from "statemesh-core";
function CartBadge() {
const count = useMeshSelector((state) =>
state.cart.items.reduce((sum, item) => sum + item.quantity, 0)
);
return <span>{count} items</span>;
}The component only re-renders when the selector's return value changes.
createSelector
For expensive selectors, use createSelector to memoize the result:
import { createSelector } from "statemesh-core";
const selectCartTotal = createSelector(
(state) => state.cart.items,
(items) => items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
function CartTotal() {
const total = useMeshSelector(selectCartTotal);
return <strong>Total: ${total.toFixed(2)}</strong>;
}createSelector takes one or more input selectors followed by a combiner function. It caches the last result and only recomputes when any input selector returns a new reference.
When to use createSelector
Use createSelector when:
- The selector does expensive computation (sorting, filtering large arrays)
- The selector is used in multiple components
- You want to avoid unnecessary recomputation on unrelated state changes
Computed Values
Computed values are named, cached derived state registered on the mesh. They automatically track their dependencies and recompute only when those dependencies change.
Define a Computed Value
mesh.computed("cart.total", {
deps: ["cart.items"],
compute: (state) => {
return state.cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
});Use in React
import { useMeshComputed } from "statemesh-core";
function CartTotal() {
const total = useMeshComputed<number>("cart.total");
return <strong>Total: ${total.toFixed(2)}</strong>;
}How Dependencies Work
The deps array specifies which state paths the computed value depends on. When any of these paths change, the computed value is marked dirty and recomputes on the next read.
mesh.computed("user.displayName", {
deps: ["user.firstName", "user.lastName"],
compute: (state) => `${state.user.firstName} ${state.user.lastName}`
});TIP
Computed values are lazy — they don't compute until someone reads them. If no component or code reads the value, no computation happens.
Dependency Intersection
If your computed value depends on paths that are subsets of each other, StateMesh uses dependency intersection to avoid redundant recomputation:
mesh.computed("cart.summary", {
deps: ["cart.items", "cart.items.length"],
compute: (state) => ({
count: state.cart.items.length,
total: state.cart.items.reduce((sum, item) => sum + item.price, 0)
})
});Registration and Replacement
Like actions, computed values are guarded against duplicate registration:
mesh.computed("cart.total", { deps: [...], compute: ... });
// Throws DuplicateRegistrationError
mesh.computed("cart.total", { deps: [...], compute: ... });
// Replace explicitly
mesh.computed("cart.total", { deps: [...], compute: ... }, { replace: true });Example: Product Catalog
const mesh = createMesh({
state: {
products: [] as Product[],
filters: { search: "", category: "all", sort: "name" }
}
});
// Computed: filtered and sorted products
mesh.computed("products.filtered", {
deps: ["products", "filters"],
compute: (state) => {
let result = state.products;
if (state.filters.search) {
const query = state.filters.search.toLowerCase();
result = result.filter((p) => p.name.toLowerCase().includes(query));
}
if (state.filters.category !== "all") {
result = result.filter((p) => p.category === state.filters.category);
}
if (state.filters.sort === "price") {
result = [...result].sort((a, b) => a.price - b.price);
} else {
result = [...result].sort((a, b) => a.name.localeCompare(b.name));
}
return result;
}
});
// Computed: total count
mesh.computed("products.count", {
deps: ["products"],
compute: (state) => state.products.length
});Important Notes
TIP
Computed values are cached until their dependencies change. Multiple reads return the same cached result without recomputing.
WARNING
If the compute function throws, the computed value notifies listeners with the last known good value and marks itself dirty for the next read.
WARNING
Avoid side effects in compute functions. They should be pure transformations of state.
Next Steps
- Batch Operations — Group multiple updates
- Transactions — Async operations with optimistic UI
- Performance — Optimization tips
