Migration Guide v5.4.0
This guide helps you migrate to the latest version of @mcabreradev/filter and understand the framework integration APIs.
Current Version: v5.4.0
What's New in v5.4.0
- Stable Framework Integrations: Production-ready React, Vue, Angular, SolidJS, and Preact support
- Improved Type Safety: Better TypeScript inference for framework hooks
- Bug Fixes: Stability improvements across all integrations
- SSR Compatibility: Full support for Next.js and Nuxt
Framework Integration APIs
React Hooks
All React hooks return consistent interfaces with filtered and isFiltering properties.
useFilter
Basic filtering hook for React components.
import { useFilter } from '@mcabreradev/filter';
interface User {
id: number;
name: string;
active: boolean;
}
function UserList({ users }: { users: User[] }) {
const { filtered, isFiltering } = useFilter(users, { active: true });
return (
<div>
{isFiltering && <span>Filtering {users.length} users...</span>}
<ul>
{filtered.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}Return Type:
interface UseFilterResult<T> {
filtered: T[]; // Filtered results
isFiltering: boolean; // True if filter is active (filtered.length !== data.length)
}useFilteredState
Hook with internal state management for both data and filter expression.
import { useFilteredState } from '@mcabreradev/filter';
function UserManager() {
const {
data,
setData,
expression,
setExpression,
filtered,
isFiltering
} = useFilteredState<User>([], () => true);
const handleAddUser = (user: User) => {
setData([...data, user]);
};
const handleFilterActive = () => {
setExpression({ active: true });
};
return (
<div>
<button onClick={handleFilterActive}>Show Active Only</button>
<p>Showing {filtered.length} of {data.length} users</p>
{/* ... */}
</div>
);
}Return Type:
interface UseFilteredStateResult<T> {
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';
function UserSearch({ users }: { users: User[] }) {
const [search, setSearch] = useState('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
{ name: { $contains: search } },
{ delay: 300 }
);
return (
<div>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search users..."
/>
{isPending && <span>Typing...</span>}
{isFiltering && <span>Filtered to {filtered.length} results</span>}
{/* ... */}
</div>
);
}Return Type:
interface UseDebouncedFilterResult<T> {
filtered: T[];
isFiltering: boolean;
isPending: boolean; // True while debounce is waiting
}usePaginatedFilter
Filtering with built-in pagination.
import { usePaginatedFilter } from '@mcabreradev/filter';
function PaginatedUserList({ users }: { users: User[] }) {
const {
filtered,
isFiltering,
data,
currentPage,
pageSize,
totalPages,
hasNextPage,
hasPreviousPage,
nextPage,
previousPage,
goToPage,
setPageSize
} = usePaginatedFilter(users, { active: true }, 10);
return (
<div>
<p>Page {currentPage} of {totalPages}</p>
{data.map(user => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={previousPage} disabled={!hasPreviousPage}>
Previous
</button>
<button onClick={nextPage} disabled={!hasNextPage}>
Next
</button>
</div>
);
}Return Type:
interface UsePaginatedFilterResult<T> extends PaginationResult<T>, PaginationActions {
filtered: T[]; // All filtered results
isFiltering: boolean;
}
interface PaginationResult<T> {
data: T[]; // Current page data
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
}
interface PaginationActions {
nextPage: () => void;
previousPage: () => void;
goToPage: (page: number) => void;
setPageSize: (size: number) => void;
}Vue Composables
Vue composables return ComputedRef and Ref for reactive integration.
useFilter
import { ref } from 'vue';
import { useFilter } from '@mcabreradev/filter';
interface User {
id: number;
name: string;
active: boolean;
}
const users = ref<User[]>([...]);
const searchTerm = ref('');
const { filtered, isFiltering } = useFilter(users, {
name: { $contains: searchTerm }
});Return Type:
interface UseFilterResult<T> {
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
}useFilteredState
import { useFilteredState } from '@mcabreradev/filter';
const {
data,
expression,
filtered,
isFiltering
} = useFilteredState<User>([], () => true);
// Update data
data.value = [...newUsers];
// Update expression
expression.value = { active: true };Return Type:
interface UseFilteredStateResult<T> {
data: Ref<T[]>;
expression: Ref<Expression<T>>;
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
}useDebouncedFilter
import { useDebouncedFilter } from '@mcabreradev/filter';
const search = ref('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
{ name: { $contains: search } },
{ delay: 300 }
);Return Type:
interface UseDebouncedFilterResult<T> {
filtered: ComputedRef<T[]>;
isFiltering: ComputedRef<boolean>;
isPending: Ref<boolean>;
}usePaginatedFilter
import { usePaginatedFilter } from '@mcabreradev/filter';
const {
filtered,
isFiltering,
pagination,
currentPage,
pageSize,
nextPage,
previousPage,
goToPage,
setPageSize
} = usePaginatedFilter(users, { active: true }, 10);Return Type:
interface UsePaginatedFilterResult<T> {
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;
}useFilteredState
import { useFilteredState } from '@mcabreradev/filter';
const {
data,
expression,
filtered,
isFiltering
} = useFilteredState<User>([], () => true);
// Update data
data.set([...newUsers]);
// Update expression
expression.set({ active: true });Return Type:
interface UseFilteredStateResult<T> {
data: Writable<T[]>;
expression: Writable<Expression<T>>;
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
}useDebouncedFilter
import { useDebouncedFilter } from '@mcabreradev/filter';
const search = writable('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
users,
{ name: { $contains: search } },
{ delay: 300 }
);Return Type:
interface UseDebouncedFilterResult<T> {
filtered: Readable<T[]>;
isFiltering: Readable<boolean>;
isPending: Readable<boolean>;
}usePaginatedFilter
import { usePaginatedFilter } from '@mcabreradev/filter';
const {
filtered,
isFiltering,
pagination,
currentPage,
pageSize,
nextPage,
previousPage,
goToPage,
setPageSize
} = usePaginatedFilter(users, { active: true }, 10);Return Type:
interface UsePaginatedFilterResult<T> {
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;
}Common Patterns
Search with Debounce
React:
function Search() {
const [search, setSearch] = useState('');
const { filtered, isPending } = useDebouncedFilter(
data,
{ name: { $contains: search } },
{ delay: 300 }
);
return (
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search..."
/>
);
}Vue:
<script setup>
import { ref } from 'vue';
import { useDebouncedFilter } from '@mcabreradev/filter';
const search = ref('');
const { filtered, isPending } = useDebouncedFilter(
data,
{ name: { $contains: search } },
{ delay: 300 }
);
</script>
<template>
<input v-model="search" placeholder="Search..." />
<span v-if="isPending">Typing...</span>
</template>Pagination with Filtering
All frameworks follow the same pattern - filter first, then paginate the results.
const {
filtered, // All filtered results
data, // Current page data
currentPage,
totalPages,
nextPage,
previousPage
} = usePaginatedFilter(allData, filterExpression, pageSize);Migration from Earlier Versions
From v5.3.0 to v5.4.0
No breaking changes. v5.4.0 is a stability and bug-fix release.
From v5.2.0 to v5.3.0
Framework integrations were added. If you were using the core filter function, no changes needed.
From v5.0.0 to v5.1.0+
Lazy evaluation functions were added. Existing code continues to work without changes.
TypeScript Support
All hooks and composables are fully typed with generics:
interface Product {
id: number;
name: string;
price: number;
}
// React
const { filtered } = useFilter<Product>(products, { price: { $gte: 100 } });
// filtered is Product[]
// Vue
const { filtered } = useFilter<Product>(products, { price: { $gte: 100 } });
// filtered is ComputedRef<Product[]>SSR Compatibility
All framework integrations are SSR-compatible:
- Next.js: Works in both App Router and Pages Router
- Nuxt: Compatible with Nuxt 3 Example with Next.js App Router:
'use client';
import { useFilter } from '@mcabreradev/filter';
export function ProductList({ products }: { products: Product[] }) {
const { filtered, isFiltering } = useFilter(products, { inStock: true });
return (
<div>
{filtered.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}Performance Tips
Use
enableCachefor large datasets:typescriptuseFilter(largeData, expression, { enableCache: true });Debounce search inputs:
typescriptuseDebouncedFilter(data, searchExpression, { delay: 300 });Paginate large result sets:
typescriptusePaginatedFilter(data, expression, 50); // 50 items per pageMemoize complex expressions:
typescriptconst expression = useMemo(() => ({ price: { $gte: minPrice, $lte: maxPrice }, category: { $in: selectedCategories } }), [minPrice, maxPrice, selectedCategories]);
Troubleshooting
React: "Too many re-renders"
Problem: Expression object is recreated on every render.
Solution: Memoize the expression:
const expression = useMemo(() => ({ active: true }), []);
const { filtered } = useFilter(data, expression);Vue: "Filtered results not updating"
Problem: Using plain objects instead of refs.
Solution: Wrap in ref() or computed():
const searchTerm = ref(''); // Not const searchTerm = '';
const { filtered } = useFilter(data, { name: { $contains: searchTerm } });Support
- Documentation: Full Documentation
- GitHub Issues: Report bugs
- GitHub Discussions: Ask questions
What's Next?
Check out the framework-specific guides: