Framework Integrations
Version: 5.8.2 Status: Stable
Complete guide for using @mcabreradev/filter with 6 major frameworks.
Table of Contents
- Overview
- Installation
- React Integration
- Vue Integration
- Svelte Integration
- Angular Integration ⭐ NEW
- SolidJS Integration ⭐ NEW
- Preact Integration ⭐ NEW
- Shared Features
- Performance Tips
- TypeScript Support
- Examples
Overview
The framework integrations provide idiomatic hooks, composables, services, and stores for all major frameworks, making it easy to integrate powerful filtering capabilities into your applications.
Supported Frameworks
- ⚛️ React - Hooks with automatic re-rendering
- 🟢 Vue - Composition API with reactivity
- 🔴 Svelte - Store-based reactive filtering
- 🅰️ Angular - Services and Pipes with Signals ⭐ NEW
- 🔷 SolidJS - Signal-based reactive hooks ⭐ NEW
- ⚡ Preact - Lightweight hooks API ⭐ NEW
Features
- React Hooks:
useFilter,useFilteredState,useDebouncedFilter,usePaginatedFilter - Vue Composables: Composition API-first with full reactivity
- Svelte Stores: Reactive stores with derived state
- Angular Services:
FilterService,DebouncedFilterService,PaginatedFilterService,FilterPipe - SolidJS Hooks:
useFilter,useDebouncedFilter,usePaginatedFilter - Preact Hooks:
useFilter,useFilteredState,useDebouncedFilter,usePaginatedFilter - Shared Utilities: Debouncing, pagination, and performance optimizations
- TypeScript: Full type safety with generics
- SSR Compatible: Works with Next.js, Nuxt, SvelteKit, Angular Universal, and SolidStart
Installation
npm install @mcabreradev/filter
# Install peer dependencies for your framework
npm install react # For React
npm install vue # For Vue
npm install svelte # For Svelte
npm install @angular/core # For Angular 17+
npm install solid-js # For SolidJS
npm install preact # For PreactReact Integration
useFilter
Basic filtering with automatic memoization.
import { useFilter } from '@mcabreradev/filter';
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
function UserList() {
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com', active: true },
{ id: 2, name: 'Bob', email: 'bob@example.com', active: false },
];
const { filtered, isFiltering } = useFilter(users, { active: true });
return (
<div>
<p>Showing {filtered.length} active users</p>
{filtered.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}API:
function useFilter<T>(
data: T[],
expression: Expression<T>,
options?: FilterOptions
): {
filtered: T[];
isFiltering: boolean;
}useFilteredState
Stateful filtering with local data management.
import { useFilteredState } from '@mcabreradev/filter';
function UserManager() {
const {
data,
setData,
expression,
setExpression,
filtered,
isFiltering,
} = useFilteredState<User>(initialUsers, { active: true });
const addUser = (user: User) => {
setData([...data, user]);
};
const filterByName = (name: string) => {
setExpression({ name: { $contains: name } });
};
return (
<div>
<input onChange={(e) => filterByName(e.target.value)} />
<button onClick={() => addUser(newUser)}>Add User</button>
<UserList users={filtered} />
</div>
);
}API:
function useFilteredState<T>(
initialData?: T[],
initialExpression?: Expression<T>,
options?: FilterOptions
): {
data: T[];
setData: (data: T[]) => void;
expression: Expression<T>;
setExpression: (expression: Expression<T>) => void;
filtered: T[];
isFiltering: boolean;
}useDebouncedFilter
Debounced filtering for search inputs.
import { useDebouncedFilter } from '@mcabreradev/filter';
import { useState } from 'react';
function SearchUsers() {
const [searchTerm, setSearchTerm] = useState('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
searchTerm,
{ delay: 300 }
);
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search users..."
/>
{isPending && <span>Searching...</span>}
<UserList users={filtered} />
</div>
);
}API:
function useDebouncedFilter<T>(
data: T[],
expression: Expression<T>,
options?: UseDebouncedFilterOptions
): {
filtered: T[];
isFiltering: boolean;
isPending: boolean;
}
interface UseDebouncedFilterOptions extends FilterOptions {
delay?: number; // Default: 300ms
}usePaginatedFilter
Filtering with built-in pagination.
import { usePaginatedFilter } from '@mcabreradev/filter';
function PaginatedUserList() {
const {
data,
filtered,
isFiltering,
currentPage,
totalPages,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
goToPage,
setPageSize,
} = usePaginatedFilter(users, { active: true }, 10);
return (
<div>
<UserList users={data} />
<div>
<button onClick={previousPage} disabled={!hasPreviousPage}>
Previous
</button>
<span>
Page {currentPage} of {totalPages}
</span>
<button onClick={nextPage} disabled={!hasNextPage}>
Next
</button>
</div>
</div>
);
}API:
function usePaginatedFilter<T>(
data: T[],
expression: Expression<T>,
initialPageSize?: number,
options?: FilterOptions
): {
data: T[];
filtered: T[];
isFiltering: boolean;
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
nextPage: () => void;
previousPage: () => void;
goToPage: (page: number) => void;
setPageSize: (size: number) => void;
}FilterProvider
Global filter configuration with React Context.
import { FilterProvider, useFilterContext } from '@mcabreradev/filter';
function App() {
return (
<FilterProvider value={{ options: { caseSensitive: true, enableCache: true } }}>
<UserList />
</FilterProvider>
);
}
function UserList() {
const context = useFilterContext();
// Use context.options in your components
}Vue Integration
useFilter
Vue 3 Composition API filtering.
<script setup lang="ts">
import { ref } from 'vue';
import { useFilter } from '@mcabreradev/filter';
interface User {
id: number;
name: string;
active: boolean;
}
const users = ref<User[]>([
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
]);
const expression = ref({ active: true });
const { filtered, isFiltering } = useFilter(users, expression);
</script>
<template>
<div>
<p>Showing {{ filtered.length }} active users</p>
<div v-for="user in filtered" :key="user.id">
{{ user.name }}
</div>
</div>
</template>API:
function useFilter<T>(
data: MaybeRef<T[]>,
expression: MaybeRef<Expression<T>>,
options?: MaybeRef<FilterOptions>
): {
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
}useFilteredState
Stateful filtering with Vue refs.
<script setup lang="ts">
import { useFilteredState } from '@mcabreradev/filter';
const { data, expression, filtered, isFiltering } = useFilteredState<User>(
initialUsers,
{ active: true }
);
const addUser = (user: User) => {
data.value = [...data.value, user];
};
const filterByName = (name: string) => {
expression.value = { name: { $contains: name } };
};
</script>API:
function useFilteredState<T>(
initialData?: T[],
initialExpression?: Expression<T>,
options?: FilterOptions
): {
data: Ref<T[]>;
expression: Ref<Expression<T>>;
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
}useDebouncedFilter
Debounced filtering for Vue.
<script setup lang="ts">
import { ref } from 'vue';
import { useDebouncedFilter } from '@mcabreradev/filter';
const searchTerm = ref('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
searchTerm,
{ delay: 300 }
);
</script>
<template>
<div>
<input v-model="searchTerm" placeholder="Search..." />
<span v-if="isPending">Searching...</span>
<UserList :users="filtered" />
</div>
</template>API:
function useDebouncedFilter<T>(
data: MaybeRef<T[]>,
expression: MaybeRef<Expression<T>>,
options?: UseDebouncedFilterOptions
): {
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
isPending: Ref<boolean>;
}usePaginatedFilter
Pagination support for Vue.
<script setup lang="ts">
import { usePaginatedFilter } from '@mcabreradev/filter';
const {
pagination,
filtered,
isFiltering,
currentPage,
pageSize,
nextPage,
previousPage,
goToPage,
setPageSize,
} = usePaginatedFilter(users, { active: true }, 10);
</script>
<template>
<div>
<UserList :users="pagination.data" />
<div>
<button @click="previousPage" :disabled="!pagination.hasPreviousPage">
Previous
</button>
<span>Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
<button @click="nextPage" :disabled="!pagination.hasNextPage">
Next
</button>
</div>
</div>
</template>API:
function usePaginatedFilter<T>(
data: MaybeRef<T[]>,
expression: MaybeRef<Expression<T>>,
initialPageSize?: number,
options?: FilterOptions
): {
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
pagination: ComputedRef<PaginationResult<T>>;
currentPage: Ref<number>;
pageSize: Ref<number>;
nextPage: () => void;
previousPage: () => void;
goToPage: (page: number) => void;
setPageSize: (size: number) => void;
}Svelte Integration
useFilter
Svelte store-based filtering.
<script lang="ts">
import { writable } from 'svelte/store';
import { useFilter } from '@mcabreradev/filter';
interface User {
id: number;
name: string;
active: boolean;
}
const users = writable<User[]>([
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
]);
const expression = writable({ active: true });
const { filtered, isFiltering } = useFilter(users, expression);
</script>
<div>
<p>Showing {$filtered.length} active users</p>
{#each $filtered as user (user.id)}
<div>{user.name}</div>
{/each}
</div>API:
function useFilter<T>(
data: T[] | Readable<T[]>,
expression: Expression<T> | Readable<Expression<T>>,
options?: FilterOptions
): {
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
}useFilteredState
Stateful filtering with Svelte stores.
<script lang="ts">
import { useFilteredState } from '@mcabreradev/filter';
const { data, expression, filtered, isFiltering } = useFilteredState<User>(
initialUsers,
{ active: true }
);
const addUser = (user: User) => {
$data = [...$data, user];
};
const filterByName = (name: string) => {
$expression = { name: { $contains: name } };
};
</script>API:
function useFilteredState<T>(
initialData?: T[],
initialExpression?: Expression<T>,
options?: FilterOptions
): {
data: Writable<T[]>;
expression: Writable<Expression<T>>;
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
}useDebouncedFilter
Debounced filtering for Svelte.
<script lang="ts">
import { writable } from 'svelte/store';
import { useDebouncedFilter } from '@mcabreradev/filter';
const searchTerm = writable('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
searchTerm,
{ delay: 300 }
);
</script>
<div>
<input bind:value={$searchTerm} placeholder="Search..." />
{#if $isPending}
<span>Searching...</span>
{/if}
<UserList users={$filtered} />
</div>API:
function useDebouncedFilter<T>(
data: T[] | Readable<T[]>,
expression: Expression<T> | Readable<Expression<T>>,
options?: UseDebouncedFilterOptions
): {
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
isPending: Readable<boolean>;
}usePaginatedFilter
Pagination support for Svelte.
<script lang="ts">
import { usePaginatedFilter } from '@mcabreradev/filter';
const {
pagination,
filtered,
isFiltering,
currentPage,
pageSize,
nextPage,
previousPage,
goToPage,
setPageSize,
} = usePaginatedFilter(users, { active: true }, 10);
</script>
<div>
<UserList users={$pagination.data} />
<div>
<button on:click={previousPage} disabled={!$pagination.hasPreviousPage}>
Previous
</button>
<span>Page {$pagination.currentPage} of {$pagination.totalPages}</span>
<button on:click={nextPage} disabled={!$pagination.hasNextPage}>
Next
</button>
</div>
</div>API:
function usePaginatedFilter<T>(
data: T[] | Readable<T[]>,
expression: Expression<T> | Readable<Expression<T>>,
initialPageSize?: number,
options?: FilterOptions
): {
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
pagination: Readable<PaginationResult<T>>;
currentPage: Writable<number>;
pageSize: Writable<number>;
nextPage: () => void;
previousPage: () => void;
goToPage: (page: number) => void;
setPageSize: (size: number) => void;
}Angular Integration
⭐ New in v5.7.0: Full Angular support with Services, Pipes, and Signals!
FilterService
Injectable service for component-based filtering with Angular Signals.
import { Component, inject } from '@angular/core';
import { FilterService } from '@mcabreradev/filter/angular';
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
@Component({
selector: 'app-user-list',
standalone: true,
providers: [FilterService],
template: `
<div>
<p>Showing {{ filterService.filtered().length }} active users</p>
@for (user of filterService.filtered(); track user.id) {
<div>{{ user.name }}</div>
}
</div>
`
})
export class UserListComponent {
filterService = inject(FilterService<User>);
users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com', active: true },
{ id: 2, name: 'Bob', email: 'bob@example.com', active: false },
];
constructor() {
this.filterService.setData(this.users);
this.filterService.setExpression({ active: true });
}
}API:
class FilterService<T> {
data: Signal<T[]>;
expression: Signal<Expression<T>>;
filtered: Signal<T[]>;
isFiltering: Signal<boolean>;
setData(data: T[]): void;
setExpression(expression: Expression<T>): void;
setOptions(options: FilterOptions): void;
}DebouncedFilterService
Debounced filtering service for search inputs.
import { Component, inject } from '@angular/core';
import { DebouncedFilterService } from '@mcabreradev/filter/angular';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-search-users',
standalone: true,
providers: [DebouncedFilterService],
template: `
<div>
<input [formControl]="searchControl" placeholder="Search users..." />
@if (filterService.isPending()) {
<span>Searching...</span>
}
@for (user of filterService.filtered(); track user.id) {
<div>{{ user.name }}</div>
}
</div>
`
})
export class SearchUsersComponent {
filterService = inject(DebouncedFilterService<User>);
searchControl = new FormControl('');
constructor() {
this.filterService.setData(users);
this.filterService.setDelay(300);
this.searchControl.valueChanges.subscribe(term => {
this.filterService.setExpression({ name: { $contains: term || '' } });
});
}
}API:
class DebouncedFilterService<T> extends FilterService<T> {
isPending: Signal<boolean>;
delay: Signal<number>;
setDelay(delay: number): void;
}PaginatedFilterService
Filtering with built-in pagination.
import { Component, inject } from '@angular/core';
import { PaginatedFilterService } from '@mcabreradev/filter/angular';
@Component({
selector: 'app-paginated-users',
standalone: true,
providers: [PaginatedFilterService],
template: `
<div>
@for (user of filterService.paginatedResults(); track user.id) {
<div>{{ user.name }}</div>
}
<div>
<button
(click)="filterService.previousPage()"
[disabled]="filterService.currentPage() === 1">
Previous
</button>
<span>
Page {{ filterService.currentPage() }} of {{ filterService.totalPages() }}
</span>
<button
(click)="filterService.nextPage()"
[disabled]="filterService.currentPage() === filterService.totalPages()">
Next
</button>
</div>
</div>
`
})
export class PaginatedUsersComponent {
filterService = inject(PaginatedFilterService<User>);
constructor() {
this.filterService.setData(users);
this.filterService.setExpression({ active: true });
this.filterService.setPageSize(10);
}
}API:
class PaginatedFilterService<T> extends FilterService<T> {
paginatedResults: Signal<T[]>;
currentPage: Signal<number>;
totalPages: Signal<number>;
pageSize: Signal<number>;
setPageSize(size: number): void;
setPage(page: number): void;
nextPage(): void;
previousPage(): void;
}FilterPipe
Transform arrays in templates with filtering.
import { Component } from '@angular/core';
import { FilterPipe } from '@mcabreradev/filter/angular';
@Component({
selector: 'app-users',
standalone: true,
imports: [FilterPipe],
template: `
<div>
@for (user of users | filter:{ active: true }; track user.id) {
<div>{{ user.name }}</div>
}
@for (product of products | filter:{ price: { $gte: 100 } } : { orderBy: 'price', limit: 10 }; track product.id) {
<div>{{ product.name }} - ${{ product.price }}</div>
}
</div>
`
})
export class UsersComponent {
users: User[] = [...];
products: Product[] = [...];
}API:
@Pipe({ name: 'filter', standalone: true })
export class FilterPipe implements PipeTransform {
transform<T>(
array: T[],
expression: Expression<T>,
options?: FilterOptions
): T[];
}SolidJS Integration
⭐ New in v5.7.0: Full SolidJS support with signal-based reactive hooks!
useFilter
Signal-based filtering with fine-grained reactivity.
import { createSignal, For } from 'solid-js';
import { useFilter } from '@mcabreradev/filter/solidjs';
interface User {
id: number;
name: string;
active: boolean;
}
function UserList() {
const [users] = createSignal<User[]>([
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
]);
const { filtered, isFiltering } = useFilter(
users,
() => ({ active: true })
);
return (
<div>
<p>Showing {filtered().length} active users</p>
<For each={filtered()}>
{(user) => <div>{user.name}</div>}
</For>
</div>
);
}API:
function useFilter<T>(
data: Accessor<T[]>,
expression: Accessor<Expression<T> | null>,
options?: Accessor<FilterOptions>
): {
filtered: Accessor<T[]>;
isFiltering: Accessor<boolean>;
}useDebouncedFilter
Debounced filtering with proper cleanup.
import { createSignal, Show, For } from 'solid-js';
import { useDebouncedFilter } from '@mcabreradev/filter/solidjs';
function SearchUsers() {
const [users] = createSignal<User[]>([...]);
const [searchTerm, setSearchTerm] = createSignal('');
const { filtered, isPending } = useDebouncedFilter(
users,
() => ({ name: { $contains: searchTerm() } }),
{ delay: 300 }
);
return (
<div>
<input
value={searchTerm()}
onInput={(e) => setSearchTerm(e.currentTarget.value)}
placeholder="Search users..."
/>
<Show when={isPending()}>
<span>Searching...</span>
</Show>
<For each={filtered()}>
{(user) => <div>{user.name}</div>}
</For>
</div>
);
}API:
function useDebouncedFilter<T>(
data: Accessor<T[]>,
expression: Accessor<Expression<T>>,
options?: {
delay?: number;
filterOptions?: FilterOptions;
}
): {
filtered: Accessor<T[]>;
isPending: Accessor<boolean>;
}usePaginatedFilter
Pagination with signal-based state management.
import { createSignal, For } from 'solid-js';
import { usePaginatedFilter } from '@mcabreradev/filter/solidjs';
function PaginatedUserList() {
const [users] = createSignal<User[]>([...]);
const {
paginatedResults,
currentPage,
totalPages,
nextPage,
prevPage,
} = usePaginatedFilter(
users,
() => ({ active: true }),
{
pageSize: 10,
initialPage: 1
}
);
return (
<div>
<For each={paginatedResults()}>
{(user) => <div>{user.name}</div>}
</For>
<div>
<button onClick={prevPage} disabled={currentPage() === 1}>
Previous
</button>
<span>
Page {currentPage()} of {totalPages()}
</span>
<button onClick={nextPage} disabled={currentPage() === totalPages()}>
Next
</button>
</div>
</div>
);
}API:
function usePaginatedFilter<T>(
data: Accessor<T[]>,
expression: Accessor<Expression<T> | null>,
options?: {
pageSize?: number;
initialPage?: number;
filterOptions?: FilterOptions;
}
): {
paginatedResults: Accessor<T[]>;
currentPage: Accessor<number>;
totalPages: Accessor<number>;
pageSize: Accessor<number>;
setPage: (page: number) => void;
setPageSize: (size: number) => void;
nextPage: () => void;
prevPage: () => void;
}Preact Integration
⭐ New in v5.7.0: Full Preact support with lightweight hooks API!
useFilter
Basic filtering hook compatible with Preact.
import { useFilter } from '@mcabreradev/filter/preact';
interface User {
id: number;
name: string;
active: boolean;
}
function UserList() {
const users: User[] = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
];
const { filtered, isFiltering } = useFilter(users, { active: true });
return (
<div>
<p>Showing {filtered.length} active users</p>
{filtered.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}API:
function useFilter<T>(
data: T[],
expression: Expression<T>,
options?: FilterOptions
): {
filtered: T[];
isFiltering: boolean;
}useFilteredState
Stateful filtering with Preact hooks.
import { useFilteredState } from '@mcabreradev/filter/preact';
function UserManager() {
const {
data,
setData,
expression,
setExpression,
filtered,
isFiltering,
} = useFilteredState<User>(initialUsers, { active: true });
const addUser = (user: User) => {
setData([...data, user]);
};
const filterByName = (name: string) => {
setExpression({ name: { $contains: name } });
};
return (
<div>
<input onInput={(e) => filterByName(e.currentTarget.value)} />
<button onClick={() => addUser(newUser)}>Add User</button>
<UserList users={filtered} />
</div>
);
}API:
function useFilteredState<T>(
initialData?: T[],
initialExpression?: Expression<T>,
options?: FilterOptions
): {
data: T[];
setData: (data: T[]) => void;
expression: Expression<T>;
setExpression: (expression: Expression<T>) => void;
filtered: T[];
isFiltering: boolean;
}useDebouncedFilter
Debounced filtering for Preact.
import { useState } from 'preact/hooks';
import { useDebouncedFilter } from '@mcabreradev/filter/preact';
function SearchUsers() {
const [searchTerm, setSearchTerm] = useState('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
{ name: { $contains: searchTerm } },
{ delay: 300 }
);
return (
<div>
<input
value={searchTerm}
onInput={(e) => setSearchTerm(e.currentTarget.value)}
placeholder="Search users..."
/>
{isPending && <span>Searching...</span>}
<UserList users={filtered} />
</div>
);
}API:
function useDebouncedFilter<T>(
data: T[],
expression: Expression<T>,
options?: UseDebouncedFilterOptions
): {
filtered: T[];
isFiltering: boolean;
isPending: boolean;
}usePaginatedFilter
Pagination support for Preact.
import { usePaginatedFilter } from '@mcabreradev/filter/preact';
function PaginatedUserList() {
const {
data,
filtered,
isFiltering,
currentPage,
totalPages,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
goToPage,
setPageSize,
} = usePaginatedFilter(users, { active: true }, 10);
return (
<div>
<UserList users={data} />
<div>
<button onClick={previousPage} disabled={!hasPreviousPage}>
Previous
</button>
<span>
Page {currentPage} of {totalPages}
</span>
<button onClick={nextPage} disabled={!hasNextPage}>
Next
</button>
</div>
</div>
);
}API:
function usePaginatedFilter<T>(
data: T[],
expression: Expression<T>,
initialPageSize?: number,
options?: FilterOptions
): {
data: T[];
filtered: T[];
isFiltering: boolean;
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
nextPage: () => void;
previousPage: () => void;
goToPage: (page: number) => void;
setPageSize: (size: number) => void;
}Shared Features
Debouncing
All frameworks support debounced filtering with configurable delays:
// Default delay: 300ms
useDebouncedFilter(data, expression);
// Custom delay
useDebouncedFilter(data, expression, { delay: 500 });
// With filter options
useDebouncedFilter(data, expression, {
delay: 300,
caseSensitive: true,
enableCache: true,
});Pagination
Pagination utilities are shared across all frameworks:
- Page navigation:
nextPage(),previousPage(),goToPage(page) - Page size:
setPageSize(size) - Metadata:
currentPage,totalPages,totalItems - Availability:
hasNextPage,hasPreviousPage
Filter Options
All hooks/composables/stores support standard filter options:
{
caseSensitive: boolean; // Default: false
maxDepth: number; // Default: 3
enableCache: boolean; // Default: false
customComparator?: (a, b) => boolean;
}Performance Tips
React / Preact
- Memoize expressions: Use
useMemofor complex expressions
const expression = useMemo(() => ({
age: { $gte: 18 },
city: { $in: ['Berlin', 'Paris'] }
}), []);- Enable caching: For large datasets
useFilter(data, expression, { enableCache: true });- Use debouncing: For search inputs
useDebouncedFilter(data, { name: { $contains: searchTerm } }, { delay: 300 });Vue
- Avoid unnecessary reactivity: Use
shallowReffor large datasets
const users = shallowRef(largeDataset);- Computed expressions: For derived filter expressions
const expression = computed(() => ({
name: { $contains: searchTerm.value }
}));- Enable caching: For repeated queries
useFilter(data, expression, { enableCache: true });Svelte
- Use derived stores: For computed values
const filtered = derived([data, expression], ([$data, $expr]) => {
return filter($data, $expr);
});Avoid store subscriptions in loops: Subscribe once at the top level
Enable caching: For large datasets
useFilter(data, expression, { enableCache: true });Angular
- Use Signals: Leverage Angular's fine-grained reactivity
const filtered = computed(() =>
filter(this.data(), this.expression())
);- Enable caching: In services for repeated queries
this.filterService.setOptions({ enableCache: true });- Use OnPush: Change detection strategy
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})SolidJS
- Fine-grained reactivity: SolidJS automatically optimizes
const { filtered } = useFilter(data, expression);- Memoize expressions: Use
createMemofor computed values
const expression = createMemo(() => ({
name: { $contains: searchTerm() }
}));- Enable caching: For large datasets
useFilter(data, expression, () => ({ enableCache: true }));TypeScript Support
All framework integrations are fully typed with generics:
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
// React / Preact
const { filtered } = useFilter<User>(users, { active: true });
// filtered is User[]
// Vue
const { filtered } = useFilter<User>(users, expression);
// filtered is ComputedRef<User[]>
// Svelte
const { filtered } = useFilter<User>(users, expression);
// filtered is Readable<User[]>
// Angular
filterService.setData<User>(users);
// filtered is Signal<User[]>
// SolidJS
const { filtered } = useFilter<User>(users, expression);
// filtered is Accessor<User[]>Examples
Real-World React Example
import { useState } from 'react';
import { useDebouncedFilter, usePaginatedFilter } from '@mcabreradev/filter';
function ProductCatalog() {
const [searchTerm, setSearchTerm] = useState('');
const [category, setCategory] = useState('all');
const expression = useMemo(() => {
const filters: any = {};
if (searchTerm) {
filters.name = { $contains: searchTerm };
}
if (category !== 'all') {
filters.category = category;
}
return filters;
}, [searchTerm, category]);
const {
data,
currentPage,
totalPages,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
} = usePaginatedFilter(products, expression, 20, { enableCache: true });
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search products..."
/>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<ProductGrid products={data} />
<Pagination
currentPage={currentPage}
totalPages={totalPages}
hasNext={hasNextPage}
hasPrevious={hasPreviousPage}
onNext={nextPage}
onPrevious={previousPage}
/>
</div>
);
}Real-World Vue Example
<script setup lang="ts">
import { ref, computed } from 'vue';
import { usePaginatedFilter } from '@mcabreradev/filter';
const searchTerm = ref('');
const category = ref('all');
const expression = computed(() => {
const filters: any = {};
if (searchTerm.value) {
filters.name = { $contains: searchTerm.value };
}
if (category.value !== 'all') {
filters.category = category.value;
}
return filters;
});
const {
pagination,
nextPage,
previousPage,
} = usePaginatedFilter(products, expression, 20, { enableCache: true });
</script>
<template>
<div>
<input v-model="searchTerm" placeholder="Search products..." />
<select v-model="category">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<ProductGrid :products="pagination.data" />
<Pagination
:current-page="pagination.currentPage"
:total-pages="pagination.totalPages"
:has-next="pagination.hasNextPage"
:has-previous="pagination.hasPreviousPage"
@next="nextPage"
@previous="previousPage"
/>
</div>
</template>Real-World Svelte Example
<script lang="ts">
import { writable, derived } from 'svelte/store';
import { usePaginatedFilter } from '@mcabreradev/filter';
const searchTerm = writable('');
const category = writable('all');
const expression = derived([searchTerm, category], ([$searchTerm, $category]) => {
const filters: any = {};
if ($searchTerm) {
filters.name = { $contains: $searchTerm };
}
if ($category !== 'all') {
filters.category = $category;
}
return filters;
});
const {
pagination,
nextPage,
previousPage,
} = usePaginatedFilter(products, expression, 20, { enableCache: true });
</script>
<div>
<input bind:value={$searchTerm} placeholder="Search products..." />
<select bind:value={$category}>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<ProductGrid products={$pagination.data} />
<Pagination
currentPage={$pagination.currentPage}
totalPages={$pagination.totalPages}
hasNext={$pagination.hasNextPage}
hasPrevious={$pagination.hasPreviousPage}
on:next={nextPage}
on:previous={previousPage}
/>
</div>SSR Compatibility
All framework integrations are compatible with server-side rendering:
- Next.js: Works with App Router and Pages Router
- Nuxt: Compatible with Nuxt 3
- SvelteKit: Full SSR support
- Angular Universal: Server-side rendering support
- SolidStart: SSR and streaming support
Next.js Example
'use client';
import { useFilter } from '@mcabreradev/filter/react';
export default function UserList({ initialUsers }: { initialUsers: User[] }) {
const { filtered } = useFilter(initialUsers, { active: true });
return <div>{/* render filtered users */}</div>;
}Nuxt Example
<script setup lang="ts">
import { useFilter } from '@mcabreradev/filter/vue';
const { data: users } = await useFetch('/api/users');
const { filtered } = useFilter(users, { active: true });
</script>SvelteKit Example
<script lang="ts">
import { useFilter } from '@mcabreradev/filter/svelte';
import { writable } from 'svelte/store';
export let data;
const users = writable(data.users);
const { filtered } = useFilter(users, { active: true });
</script>Angular Universal Example
import { Component, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { FilterService } from '@mcabreradev/filter/angular';
@Component({
selector: 'app-users',
providers: [FilterService]
})
export class UsersComponent {
platformId = inject(PLATFORM_ID);
filterService = inject(FilterService<User>);
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
this.filterService.setData(this.users);
}
}
}SolidStart Example
import { createSignal } from 'solid-js';
import { useFilter } from '@mcabreradev/filter/solidjs';
export default function UserList(props: { users: User[] }) {
const [users] = createSignal(props.users);
const { filtered } = useFilter(users, () => ({ active: true }));
return <div>{/* render filtered users */}</div>;
}Migration Guide
From Array.filter()
// Before
const filtered = users.filter(user => user.active);
// After (React / Preact)
const { filtered } = useFilter(users, { active: true });
// After (Vue)
const { filtered } = useFilter(users, { active: true });
// After (Svelte)
const { filtered } = useFilter(users, { active: true });
// After (Angular)
this.filterService.setExpression({ active: true });
// After (SolidJS)
const { filtered } = useFilter(users, () => ({ active: true }));From Custom Hooks
// Before (custom React hook)
function useFilteredUsers(users: User[], searchTerm: string) {
return useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
}
// After
const { filtered } = useDebouncedFilter(
users,
{ name: { $contains: searchTerm } },
{ delay: 300 }
);Migration Guide
From Array.filter()
// Before
const filtered = users.filter(user => user.active);
// After (React)
const { filtered } = useFilter(users, { active: true });
// After (Vue)
const { filtered } = useFilter(users, { active: true });
// After (Svelte)
const { filtered } = useFilter(users, { active: true });From Custom Hooks
// Before (custom React hook)
function useFilteredUsers(users: User[], searchTerm: string) {
return useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
}
// After
const { filtered } = useDebouncedFilter(
users,
{ name: { $contains: searchTerm } },
{ delay: 300 }
);Support
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ for the JavaScript/TypeScript community