Customizing Filters
Filter Sphere allows you to extend the default filtering capabilities with your own specific logic. This feature is particularly useful when you need to implement complex or domain-specific filtering that isn’t covered by the built-in filters.
Defining a Custom Filter
Section titled “Defining a Custom Filter”To add custom filters to Filter Sphere, you can use the defineTypedFn function. This allows you to create type-safe, custom filter functions.
Use defineTypedFn to create a custom filter:
const customFilter = defineTypedFn({ name: "your filter name", define: z.function({ input: [schemaToFilter, optionalUserInputSchema], output: z.boolean(), }), implement: (value, userInput) => { // Your filter logic here return value === userInput; },});- name: A string describing your filter. Ensure it is unique and doesn’t duplicate any built-in filter names.
- By default, the name will be used as the filter label in the UI. You can override this by overriding the
mapFilterNameproperty in theuseFilterSpherehook.
- By default, the name will be used as the filter label in the UI. You can override this by overriding the
- define: A Zod schema defining the function signature.
- First argument: The schema of the data being filtered.
- Second argument (optional): The schema for user input, if required.
- implement: The actual filter logic.
Using Custom Filters
Section titled “Using Custom Filters”Add your custom filters to the filterFnList when using the useFilterSphere hook:
import { useFilterSphere, presetFilter } from "@fn-sphere/filter";
const { predicate, context } = useFilterSphere({ schema: yourSchema, filterFnList: [ customFilter, // Other custom filters... ...presetFilter, // Include preset filters if needed ],});This approach allows you to extend Filter Sphere’s functionality with your own custom filtering logic while maintaining type safety and integration with the existing system.
Here’s an example of using a custom filter function to filter the average of an array of numbers:
| scores | average |
|---|---|
| [1] | 1 |
| [1,10] | 5.5 |
| [1,2,3,4,5] | 3 |
| [6,7,8,9,10] | 8 |
import { z } from "zod";import { Table } from "~/components/table";import { FilterSphereProvider, FilterBuilder, useFilterSphere, presetFilter, defineTypedFn, type FnSchema,} from "@fn-sphere/filter";
const data = [ { scores: [1] }, { scores: [1, 10] }, { scores: [1, 2, 3, 4, 5] }, { scores: [6, 7, 8, 9, 10] },].map((item, index) => ({ ...item, average: item.scores.reduce((acc, curr) => acc + curr) / item.scores.length,}));
const schema = z.object({ scores: z.array(z.number()),});
const dataFilters: FnSchema[] = [ // Define a custom filter function defineTypedFn({ name: "Average score is greater than", define: z.function({ // The first argument is the schema that needs filtering // and the second argument is the user input input: [z.array(z.number()), z.number()], output: z.boolean(), }), implement: (value, input) => { // Implement the filter logic const sum = value.reduce((acc, cur) => acc + cur); return sum / value.length > input; }, }), // Preset filters contains all the built-in filters ...presetFilter,];
export default function AdvancedFilter() { const { predicate, context } = useFilterSphere({ schema, // Pass the custom filters to the filterFnList filterFnList: dataFilters, }); return ( <FilterSphereProvider context={context}> <FilterBuilder /> <Table data={data.filter(predicate)} /> </FilterSphereProvider> );}Define a Generic Filter
Section titled “Define a Generic Filter”Generic filters work across multiple data types, unlike custom filters that are bound to specific schemas. For example, the built-in equals filter works with strings, numbers, booleans, and union types without needing separate implementations.
Use defineGenericFn to create a generic filter:
| name | age | active |
|---|---|---|
| Alice | 25 | true |
| Bob | 30 | false |
| alice | 35 | true |
| ALICE | 40 | false |
import { z } from "zod";import { Table } from "~/components/table";import { FilterSphereProvider, FilterBuilder, useFilterSphere, defineGenericFn, type FnSchema,} from "@fn-sphere/filter";import type { $ZodBoolean, $ZodString, $ZodNumber, $ZodUnion, $ZodLiteral,} from "zod/v4/core";
const data = [ { name: "Alice", age: 25, active: true }, { name: "Bob", age: 30, active: false }, { name: "alice", age: 35, active: true }, { name: "ALICE", age: 40, active: false },];
const schema = z.object({ name: z.string(), age: z.number(), active: z.boolean(),});
const genericFilter = defineGenericFn({ name: "generic equals", // Define which types this filter can work with genericLimit: (genericT): genericT is $ZodBoolean | $ZodString | $ZodNumber => genericT._zod.def.type === "boolean" || genericT._zod.def.type === "string" || genericT._zod.def.type === "number", // Define the function signature based on the type define: (genericT) => z.function({ input: [genericT, genericT], output: z.boolean(), }), // Implement logic that handles all types implement: ( value: boolean | string | number, target: boolean | string | number, ) => { if (typeof value === "string" && typeof target === "string") return value.toLowerCase() === target.toLowerCase(); return value === target; },});
export default function GenericFilterExample() { const { predicate, context } = useFilterSphere({ schema, filterFnList: [genericFilter], }); return ( <FilterSphereProvider context={context}> <FilterBuilder /> <Table data={data.filter(predicate)} /> </FilterSphereProvider> );}Key properties:
- name: A unique string identifying your filter.
- genericLimit: A type guard that determines which Zod types this filter applies to.
- define: A function that receives the matched type and returns a Zod function schema.
- implement: The filter logic that handles all specified types.