Skip to content

Best Practices

Guidelines and patterns for using @mcabreradev/filter effectively.

General Principles

1. Use Type-Safe Expressions

Always provide generic type parameters for type safety:

typescript
interface User {
  id: number;
  name: string;
  age: number;
  status: 'active' | 'inactive';
}

const { filtered } = useFilter<User>(data, expression);

2. Memoize Expressions

Prevent unnecessary re-renders by memoizing expressions:

typescript
const expression = useMemo(() => ({
  age: { $gte: 18 },
  status: { $eq: 'active' }
}), []);

const { filtered } = useFilter(data, expression);

3. Memoize Configuration

Memoize options objects to prevent re-computation:

typescript
const options = useMemo(() => ({
  memoize: true,
  caseSensitive: false
}), []);

const { filtered } = useFilter(data, expression, options);

4. Use Appropriate Operators

Choose the right operator for your use case:

typescript
const expression = {
  age: { $gte: 18 },
  name: { $regex: /john/i },
  tags: { $contains: 'javascript' },
  role: { $in: ['admin', 'user'] }
};

React Best Practices

Hook Dependencies

Always include proper dependencies:

typescript
const expression = useMemo(() => ({
  status: { $eq: filterStatus }
}), [filterStatus]);

useEffect(() => {
  console.log('Filtered data:', filtered);
}, [filtered]);

Avoid Inline Objects

Don't create objects inline in render:

typescript
const MyComponent = () => {
  const { filtered } = useFilter(data, { age: { $gte: 18 } });

  return <UserList users={filtered} />;
};

const MyComponent = () => {
  const expression = useMemo(() => ({
    age: { $gte: 18 }
  }), []);

  const { filtered } = useFilter(data, expression);

  return <UserList users={filtered} />;
};

Combine with Other Hooks

Integrate seamlessly with other React patterns:

typescript
const UserFilter = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [minAge, setMinAge] = useState(18);

  const expression = useMemo(() => ({
    $and: [
      { name: { $regex: new RegExp(searchTerm, 'i') } },
      { age: { $gte: minAge } }
    ]
  }), [searchTerm, minAge]);

  const { filtered, isFiltering } = useFilter(users, expression, {
    memoize: true
  });

  return (
    <div>
      <input
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <input
        type="number"
        value={minAge}
        onChange={(e) => setMinAge(Number(e.target.value))}
      />
      {isFiltering ? <Spinner /> : <UserList users={filtered} />}
    </div>
  );
};

Debounce user input to reduce filtering frequency:

typescript
const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');

  const expression = useMemo(() => ({
    name: { $regex: new RegExp(searchTerm, 'i') }
  }), [searchTerm]);

  const { filtered, isPending } = useDebouncedFilter(users, expression, {
    delay: 300,
    memoize: true
  });

  return (
    <div>
      <input
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search..."
      />
      {isPending && <Spinner />}
      <UserList users={filtered} />
    </div>
  );
};

Implement Pagination

Use pagination for large datasets:

typescript
const PaginatedList = () => {
  const {
    filtered,
    currentPage,
    totalPages,
    nextPage,
    previousPage,
    goToPage
  } = usePaginatedFilter(users, expression, 25, {
    memoize: true
  });

  return (
    <div>
      <UserList users={filtered} />
      <Pagination
        current={currentPage}
        total={totalPages}
        onNext={nextPage}
        onPrevious={previousPage}
        onGoTo={goToPage}
      />
    </div>
  );
};

Vue Best Practices

Use Reactive References

Ensure data and expressions are reactive:

typescript
import { ref, computed } from 'vue';
import { useFilter } from '@mcabreradev/filter/vue';

const users = ref<User[]>([]);
const searchTerm = ref('');

const expression = computed(() => ({
  name: { $regex: new RegExp(searchTerm.value, 'i') }
}));

const { filtered } = useFilter(users, expression);

Computed Properties

Use computed for derived state:

typescript
const activeUsers = computed(() => {
  const expression = { status: { $eq: 'active' } };
  const { filtered } = useFilter(users.value, expression);
  return filtered.value;
});

Watch for Changes

React to filter changes:

typescript
import { watch } from 'vue';

const { filtered } = useFilter(users, expression);

watch(filtered, (newFiltered) => {
  console.log('Filtered data changed:', newFiltered);
});

Svelte Best Practices

Use Stores

Leverage Svelte stores for reactivity:

typescript
import { writable } from 'svelte/store';
import { useFilter } from '@mcabreradev/filter/svelte';

const users = writable<User[]>([]);
const expression = writable({ status: { $eq: 'active' } });

const { filtered } = useFilter(users, expression);

Reactive Statements

Use reactive statements for derived values:

svelte
<script lang="ts">
  import { useFilter } from '@mcabreradev/filter/svelte';

  let searchTerm = '';

  $: expression = {
    name: { $regex: new RegExp(searchTerm, 'i') }
  };

  const { filtered } = useFilter(users, expression);
</script>

<input bind:value={searchTerm} />

{#each $filtered as user}
  <div>{user.name}</div>
{/each}

Performance Optimization

Enable Memoization for Large Datasets

typescript
const { filtered } = useFilter(largeDataset, expression, {
  memoize: true
});

Use Lazy Evaluation

For very large datasets, use lazy evaluation:

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

const lazyFilter = createLazyFilter(data, expression);
const first100 = lazyFilter.take(100).toArray();

Paginate Large Results

typescript
const { filtered } = usePaginatedFilter(data, expression, 50);

Clear Cache When Needed

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

useEffect(() => {
  return () => {
    clearMemoizationCache();
  };
}, []);

Expression Patterns

Simple Equality

typescript
const expression = {
  status: { $eq: 'active' }
};

Multiple Conditions (AND)

typescript
const expression = {
  $and: [
    { age: { $gte: 18 } },
    { status: { $eq: 'active' } },
    { role: { $in: ['admin', 'user'] } }
  ]
};

Multiple Conditions (OR)

typescript
const expression = {
  $or: [
    { role: { $eq: 'admin' } },
    { permissions: { $contains: 'write' } }
  ]
};

Negation

typescript
const expression = {
  $not: {
    status: { $eq: 'deleted' }
  }
};

Nested Objects

typescript
const expression = {
  'address.city': { $eq: 'New York' },
  'profile.age': { $gte: 18 }
};

const expression = {
  address: {
    city: { $eq: 'New York' }
  }
};

Array Operations

typescript
const expression = {
  tags: { $contains: 'javascript' },
  roles: { $in: ['admin', 'moderator'] }
};

String Matching

typescript
const expression = {
  name: { $regex: /john/i },
  email: { $endsWith: '@example.com' },
  username: { $startsWith: 'user_' }
};

Range Queries

typescript
const expression = {
  $and: [
    { age: { $gte: 18 } },
    { age: { $lte: 65 } }
  ]
};

Error Handling

Validate Data

typescript
const { filtered } = useFilter(data ?? [], expression);

Handle Empty Results

typescript
const { filtered } = useFilter(data, expression);

if (filtered.length === 0) {
  return <EmptyState />;
}

return <UserList users={filtered} />;

Debug Mode

Enable debug mode during development:

typescript
const { filtered } = useFilter(data, expression, {
  debug: process.env.NODE_ENV === 'development'
});

Testing

Test Filter Logic

typescript
import { render, screen } from '@testing-library/react';
import { useFilter } from '@mcabreradev/filter/react';

test('filters users by age', () => {
  const TestComponent = () => {
    const { filtered } = useFilter(mockUsers, {
      age: { $gte: 18 }
    });

    return <div>{filtered.length}</div>;
  };

  render(<TestComponent />);
  expect(screen.getByText('2')).toBeInTheDocument();
});

Mock the Hook

typescript
jest.mock('@mcabreradev/filter/react', () => ({
  useFilter: jest.fn(() => ({
    filtered: mockFilteredData,
    isFiltering: false
  }))
}));

Test Edge Cases

typescript
test('handles empty arrays', () => {
  const { filtered } = useFilter([], expression);
  expect(filtered).toEqual([]);
});

test('handles null values', () => {
  const data = [{ age: null }, { age: 25 }];
  const { filtered } = useFilter(data, { age: { $gt: 20 } });
  expect(filtered).toEqual([{ age: 25 }]);
});

Code Organization

Extract Filter Logic

typescript
const useUserFilters = (users: User[], filters: UserFilters) => {
  const expression = useMemo(() => ({
    $and: [
      filters.status && { status: { $eq: filters.status } },
      filters.minAge && { age: { $gte: filters.minAge } },
      filters.searchTerm && {
        name: { $regex: new RegExp(filters.searchTerm, 'i') }
      }
    ].filter(Boolean)
  }), [filters]);

  return useFilter(users, expression, { memoize: true });
};

Create Reusable Expressions

typescript
const ACTIVE_USERS_EXPRESSION = {
  status: { $eq: 'active' }
};

const ADULT_USERS_EXPRESSION = {
  age: { $gte: 18 }
};

const ACTIVE_ADULT_USERS_EXPRESSION = {
  $and: [
    ACTIVE_USERS_EXPRESSION,
    ADULT_USERS_EXPRESSION
  ]
};

Use Configuration Builder

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

const getFilterConfig = () => createFilterConfig()
  .withMemoization(true)
  .withCaseSensitivity(false)
  .withDebug(process.env.NODE_ENV === 'development')
  .build();

const config = getFilterConfig();

Common Pitfalls

❌ Creating Objects in Render

typescript
const { filtered } = useFilter(data, { age: { $gte: 18 } });

✅ Memoize Expressions

typescript
const expression = useMemo(() => ({ age: { $gte: 18 } }), []);
const { filtered } = useFilter(data, expression);

❌ Missing Dependencies

typescript
useEffect(() => {
  console.log(filtered);
}, []);

✅ Include All Dependencies

typescript
useEffect(() => {
  console.log(filtered);
}, [filtered]);

❌ Not Handling Empty Data

typescript
const { filtered } = useFilter(data, expression);

✅ Provide Default Values

typescript
const { filtered } = useFilter(data ?? [], expression);

Released under the MIT License.