Skip to content

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.

typescript
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:

typescript
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.

typescript
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:

typescript
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.

typescript
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:

typescript
interface UseDebouncedFilterResult<T> {
  filtered: T[];
  isFiltering: boolean;
  isPending: boolean;  // True while debounce is waiting
}

usePaginatedFilter ​

Filtering with built-in pagination.

typescript
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:

typescript
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 ​

typescript
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:

typescript
interface UseFilterResult<T> {
  filtered: ComputedRef<T[]>;
  isFiltering: ComputedRef<boolean>;
}

useFilteredState ​

typescript
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:

typescript
interface UseFilteredStateResult<T> {
  data: Ref<T[]>;
  expression: Ref<Expression<T>>;
  filtered: ComputedRef<T[]>;
  isFiltering: ComputedRef<boolean>;
}

useDebouncedFilter ​

typescript
import { useDebouncedFilter } from '@mcabreradev/filter';

const search = ref('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
  users,
  { name: { $contains: search } },
  { delay: 300 }
);

Return Type:

typescript
interface UseDebouncedFilterResult<T> {
  filtered: ComputedRef<T[]>;
  isFiltering: ComputedRef<boolean>;
  isPending: Ref<boolean>;
}

usePaginatedFilter ​

typescript
import { usePaginatedFilter } from '@mcabreradev/filter';

const {
  filtered,
  isFiltering,
  pagination,
  currentPage,
  pageSize,
  nextPage,
  previousPage,
  goToPage,
  setPageSize
} = usePaginatedFilter(users, { active: true }, 10);

Return Type:

typescript
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 ​

typescript
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:

typescript
interface UseFilteredStateResult<T> {
  data: Writable<T[]>;
  expression: Writable<Expression<T>>;
  filtered: Readable<T[]>;
  isFiltering: Readable<boolean>;
}

useDebouncedFilter ​

typescript
import { useDebouncedFilter } from '@mcabreradev/filter';

const search = writable('');
const { filtered, isFiltering, isPending } = useDebouncedFilter(
  users,
  { name: { $contains: search } },
  { delay: 300 }
);

Return Type:

typescript
interface UseDebouncedFilterResult<T> {
  filtered: Readable<T[]>;
  isFiltering: Readable<boolean>;
  isPending: Readable<boolean>;
}

usePaginatedFilter ​

typescript
import { usePaginatedFilter } from '@mcabreradev/filter';

const {
  filtered,
  isFiltering,
  pagination,
  currentPage,
  pageSize,
  nextPage,
  previousPage,
  goToPage,
  setPageSize
} = usePaginatedFilter(users, { active: true }, 10);

Return Type:

typescript
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:

typescript
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:

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.

typescript
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:

typescript
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:
typescript
'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 ​

  1. Use enableCache for large datasets:

    typescript
    useFilter(largeData, expression, { enableCache: true });
  2. Debounce search inputs:

    typescript
    useDebouncedFilter(data, searchExpression, { delay: 300 });
  3. Paginate large result sets:

    typescript
    usePaginatedFilter(data, expression, 50); // 50 items per page
  4. Memoize complex expressions:

    typescript
    const 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:

typescript
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():

typescript
const searchTerm = ref(''); // Not const searchTerm = '';
const { filtered } = useFilter(data, { name: { $contains: searchTerm } });

Support ​


What's Next? ​

Check out the framework-specific guides:

Released under the MIT License.