Examples
Cookies Integration
Persist filter state in cookies using @jlnstack/cookies
Cookies Integration
Use @jlnstack/cookies to persist filter state across sessions.
Setup
Make sure you have both packages installed:
pnpm add @jlnstack/filter @jlnstack/cookiesDefine the Cookie
Create a cookie to store the filter tree (without IDs):
import { createCookie } from "@jlnstack/cookies/next"
import { z } from "zod"
export const userFiltersCookie = createCookie(
"user-filters",
z.record(z.any()).optional()
)Server Component
Load initial filters from the cookie:
import { userFiltersCookie } from "./cookies"
import { UsersTable } from "./users-table"
export default async function UsersPage() {
const savedFilters = await userFiltersCookie.get()
return <UsersTable initialFilter={savedFilters ?? undefined} />
}Client Component
Sync filter changes to the cookie:
"use client"
import { isGroup, type GroupInput } from "@jlnstack/filter"
import { createFilterHooks, FilterProvider } from "@jlnstack/filter/react"
import { userFilters } from "./filters"
const hooks = createFilterHooks(userFilters)
interface Props {
initialFilter?: GroupInput<typeof userFilters>
}
function toInput(group) {
return {
type: "group",
operator: group.operator,
filters: group.filters.map((expr) =>
isGroup(expr)
? toInput(expr)
: { type: "condition", field: expr.field, value: expr.value }
),
}
}
export function UsersTable({ initialFilter }: Props) {
const filter = hooks.useFilter({
defaultFilter: initialFilter,
onFilterChange: async (filterTree) => {
await fetch("/api/save-filters", {
method: "POST",
body: JSON.stringify(toInput(filterTree)),
})
},
})
return (
<FilterProvider {...filter}>
<FilterToolbar />
<DataTable />
</FilterProvider>
)
}API Route
Create an API route to save filters:
import { userFiltersCookie } from "@/cookies"
import { NextResponse } from "next/server"
export async function POST(request: Request) {
const filters = await request.json()
await userFiltersCookie.set(filters)
return NextResponse.json({ ok: true })
}Alternative: Server Action
Use a Server Action instead of an API route:
"use server"
import type { GroupInput } from "@jlnstack/filter"
import { userFiltersCookie } from "./cookies"
import { userFilters } from "./filters"
export async function saveFilters(filters: GroupInput<typeof userFilters>) {
await userFiltersCookie.set(filters)
}"use client"
import { saveFilters } from "./actions"
export function UsersTable({ initialFilter }: Props) {
// Reuse toInput from the previous example.
const filter = hooks.useFilter({
defaultFilter: initialFilter,
onFilterChange: (filterTree) => saveFilters(toInput(filterTree)),
})
// ...
}Benefits
- Persistent preferences — Filters survive browser restarts
- No URL clutter — Keep URLs clean while maintaining state
- Server-side access — Read filters in Server Components
- Type-safe — Full validation with Zod schemas