statekit • Page 9 of 14
Selectors
17/10/2026 • 2 min read
A selector is a named read. It gets data, state, S and the machine's id,
and returns something derived from them:
import { createMachine } from "@statekit/core"
type Product = {
id: number
name: string
price: number
inStock: boolean
}
const catalogMachine = createMachine({
states: ["browsing", "checkingOut"],
data: {
products: [] as Product[],
query: "",
},
events: {
search: ({ draft }, query: string) => {
draft.query = query
},
},
selectors: {
results: ({ data }) =>
data.products.filter((product) =>
product.name
.toLowerCase()
.includes(data.query.toLowerCase())
),
byId: ({ data }, id: number) =>
data.products.find(
(product) => product.id === id
),
canEditCart: ({ state, S }) =>
(state & S.checkingOut) === 0,
},
})
catalogMachine.select.results()
catalogMachine.select.byId(42)
catalogMachine.select.canEditCart()
As with events, extra parameters become the selector's arguments, typed.
Why not a function next to the machine?
getResults(catalogMachine.data) works too. What a selector adds:
- It travels with the machine, so whatever imports the machine gets its reads too.
- In React,
m.selectis tracked likem.data. A component that calls a selector subscribes to the fields the selector read, so it rerenders when one of those changes. See rerenders. - Decorations can add them. The TanStack Query decoration adds a selector that reads a field and fetches it when it's missing or stale.
Keep them cheap
A selector runs every time it's called. Nothing is memoised, so put expensive derived values in data, written by the event that changes their inputs.
#javascript, #npm, #react, #readme, #typescript, #web-development