Common questions about @mcabreradev/filter.
A TypeScript-first filtering library that provides powerful, type-safe filtering capabilities for JavaScript arrays with framework integrations for React and Vue.
Advantages:
- Type-safe expressions with TypeScript
- Advanced operators (30+ including comparison, array, string, logical, geospatial, datetime)
- Built-in memoization for performance
- Framework-specific hooks/composables
- Lazy evaluation support
- Pagination and debouncing utilities
- Nested object filtering
- Complex logical operations
- Geospatial queries ($near, $geoBox, $geoPolygon)
- DateTime filtering ($recent, $upcoming, $dayOfWeek, $age)
Yes. The library is:
- Fully tested (613+ tests, 100% coverage)
- Type-safe with TypeScript
- Used in production applications
- Actively maintained
- Semantic versioning
- Core: ~3KB gzipped
- React integration: ~1KB additional
- Vue integration: ~1KB additional
Tree-shaking ensures you only bundle what you use.
Any modern package manager works:
npm install @mcabreradev/filter
pnpm add @mcabreradev/filter
yarn add @mcabreradev/filter
bun add @mcabreradev/filterNo, but it's recommended. The library works with JavaScript but provides excellent TypeScript support with full type inference.
Core: None
React: react ^18.0.0
Vue: vue ^3.0.0
- React 16/17: May work but not officially supported
- Vue 2: Not supported (use Vue 3)
Use the $and operator:
const expression = {
$and: [
{ age: { $gte: 18 } },
{ status: { $eq: 'active' } },
{ role: { $in: ['admin', 'user'] } }
]
};Use dot notation or nested objects:
const expression = {
'address.city': { $eq: 'New York' }
};
const expression = {
address: {
city: { $eq: 'New York' }
}
};Use $regex with the i flag:
const expression = {
name: { $regex: /john/i }
};Or configure globally:
const { filtered } = useFilter(data, expression, {
caseSensitive: false
});Yes, extend the operator system:
import { registerOperator } from '@mcabreradev/filter';
registerOperator('$between', (value, [min, max]) => {
return value >= min && value <= max;
});
const expression = {
age: { $between: [18, 65] }
};Use the $contains or $in operators:
const expression = {
tags: { $contains: 'javascript' }
};
const expression = {
'items.0.name': { $eq: 'Product A' }
};Use geospatial operators (v5.6.0+):
// Find items within 5km radius
const expression = {
location: {
$near: {
center: { lat: 52.52, lng: 13.405 },
maxDistanceMeters: 5000
}
}
};
// Find items in bounding box
const expression = {
location: {
$geoBox: {
southwest: { lat: 52.5, lng: 13.3 },
northeast: { lat: 52.6, lng: 13.5 }
}
}
};Use datetime operators (v5.6.0+):
// Events in next 7 days
const expression = {
date: { $upcoming: { days: 7 } }
};
// Recent events (last 24 hours)
const expression = {
date: { $recent: { hours: 24 } }
};
// Weekday events only
const expression = {
date: { $dayOfWeek: [1, 2, 3, 4, 5] }
};
// Business hours (9 AM - 5 PM)
const expression = {
startTime: { $timeOfDay: { start: 9, end: 17 } }
};
// Users 18 years or older
const expression = {
birthDate: { $age: { min: 18 } }
};Enable memoization when:
- Dataset has 1,000+ items
- Same expression used repeatedly
- Filter operations are expensive
const { filtered } = useFilter(data, expression, {
memoize: true
});Strategies:
- Enable memoization
- Use pagination
- Use lazy evaluation
- Debounce user input
const { filtered } = usePaginatedFilter(data, expression, 50, {
memoize: true
});Only when dependencies change. Use useMemo for expressions:
const expression = useMemo(() => ({
status: { $eq: 'active' }
}), []);
const { filtered } = useFilter(data, expression);import { clearMemoizationCache } from '@mcabreradev/filter';
clearMemoizationCache();Or disable memoization:
const { filtered } = useFilter(data, expression, {
memoize: false
});Standard WGS84 coordinates (latitude/longitude):
const location = {
lat: 52.52, // Latitude: -90 to 90
lng: 13.405 // Longitude: -180 to 180
};
const expression = {
location: {
$near: {
center: location,
maxDistanceMeters: 5000 // Always in meters
}
}
};Uses the spherical law of cosines with Earth radius = 6,371,000 meters. Accuracy:
- < 100km: ~99.9% accurate
- 100-1000km: ~99.5% accurate
- > 1000km: ~99% accurate
For most use cases (restaurant finders, delivery zones, store locators), this is highly accurate.
Convert to meters:
const milestoMeters = (miles: number) => miles * 1609.34;
const expression = {
location: {
$near: {
center: userLocation,
maxDistanceMeters: milestoMeters(5) // 5 miles
}
}
};Use $geoPolygon:
const expression = {
location: {
$geoPolygon: {
points: [
{ lat: 51.5074, lng: -0.1278 },
{ lat: 51.5100, lng: -0.1200 },
{ lat: 51.5050, lng: -0.1150 },
{ lat: 51.5020, lng: -0.1250 }
]
}
}
};All datetime operators use the local timezone of the Date objects. For UTC:
// Convert to UTC
const data = rawData.map(item => ({
...item,
date: new Date(Date.UTC(
item.date.getFullYear(),
item.date.getMonth(),
item.date.getDate()
))
}));Days are numbered 0-6:
- 0 = Sunday
- 1 = Monday
- 2 = Tuesday
- 3 = Wednesday
- 4 = Thursday
- 5 = Friday
- 6 = Saturday
// Weekdays (Monday-Friday)
const expression = {
date: { $dayOfWeek: [1, 2, 3, 4, 5] }
};
// Or use convenience operators
const expression = {
date: { $isWeekday: true }
};Yes, use $and:
const expression = {
$and: [
{
location: {
$near: {
center: userLocation,
maxDistanceMeters: 5000
}
}
},
{
eventDate: { $upcoming: { days: 7 } }
},
{
eventDate: { $dayOfWeek: [1, 2, 3, 4, 5] } // Weekdays only
}
]
};Use $age:
// Users 18-65 years old
const expression = {
birthDate: {
$age: {
min: 18,
max: 65,
unit: 'years' // 'years', 'months', or 'days'
}
}
};$recent: Relative to current time (e.g., "last 7 days")$isBefore: Absolute comparison to specific date
// Relative: Events in last 7 days
const expression = {
date: { $recent: { days: 7 } }
};
// Absolute: Events before Jan 1, 2025
const expression = {
date: { $isBefore: new Date('2025-01-01') }
};Enable memoization when:
- Dataset has 1,000+ items
- Same expression used repeatedly
- Filter operations are expensive
const { filtered } = useFilter(data, expression, {
memoize: true
});Strategies:
- Enable memoization
- Use pagination
- Use lazy evaluation
- Debounce user input
const { filtered } = usePaginatedFilter(data, expression, 50, {
memoize: true
});Only when dependencies change. Use useMemo for expressions:
const expression = useMemo(() => ({
status: { $eq: 'active' }
}), []);
const { filtered } = useFilter(data, expression);import { clearMemoizationCache } from '@mcabreradev/filter';
clearMemoizationCache();Or disable memoization:
const { filtered } = useFilter(data, expression, {
memoize: false
});Yes, use the core functions:
import { filter } from '@mcabreradev/filter';
const filtered = filter(data, expression);Yes, works with both App Router and Pages Router:
'use client';
import { useFilter } from '@mcabreradev/filter/react';
export default function Page() {
const { filtered } = useFilter(data, expression);
return <div>{/* ... */}</div>;
}Yes, use in components:
<script setup lang="ts">
import { useFilter } from '@mcabreradev/filter/vue';
const { filtered } = useFilter(data, expression);
</script>Yes, the React integration works with React Native:
import { useFilter } from '@mcabreradev/filter/react';Provide a generic type parameter:
interface User {
id: number;
name: string;
age: number;
}
const { filtered } = useFilter<User>(data, expression);Use the Expression type:
import type { Expression } from '@mcabreradev/filter';
const expression: Expression<User> = {
age: { $gte: 18 }
};Common causes:
- Missing generic type parameter
- Incorrect operator syntax
- Property doesn't exist on type
- Incompatible value types
Solution: Provide explicit types and check operator syntax.
Yes:
function FilteredList<T>({ data, expression }: Props<T>) {
const { filtered } = useFilter<T>(data, expression);
return <div>{/* ... */}</div>;
}The library is 100% backward compatible - no breaking changes! All v3.x and v4.x code continues to work.
See the Migration Guide for details on new features.
What's New in v5.x (all optional):
- v5.6.0: Geospatial and datetime operators
- v5.5.0: Array OR syntax, visual debugging
- v5.4.0: Framework integrations
- v5.2.0: Logical operators, memoization
- v5.1.0: Lazy evaluation
- v5.0.0: MongoDB-style operators
No migration needed! Just upgrade and all existing code works:
npm install @mcabreradev/filter@latestAll v3.x/v4.x syntax remains valid:
// All these still work in v5.x
filter(data, 'string');
filter(data, { prop: 'value' });
filter(data, (item) => true);
filter(data, '%pattern%');
// Plus new v5.x features (optional)
filter(data, { age: { $gte: 18 } });
filter(data, { location: { $near: { center, maxDistanceMeters: 5000 } } });Before:
const filtered = data.filter(item =>
item.age >= 18 && item.status === 'active'
);After:
const expression = {
$and: [
{ age: { $gte: 18 } },
{ status: { $eq: 'active' } }
]
};
const { filtered } = useFilter(data, expression);React Testing Library:
import { render, screen } from '@testing-library/react';
import { useFilter } from '@mcabreradev/filter/react';
test('filters data correctly', () => {
const TestComponent = () => {
const { filtered } = useFilter(mockData, expression);
return <div>{filtered.length}</div>;
};
render(<TestComponent />);
expect(screen.getByText('2')).toBeInTheDocument();
});jest.mock('@mcabreradev/filter/react', () => ({
useFilter: jest.fn(() => ({
filtered: mockFilteredData,
isFiltering: false
}))
}));Yes, fully compatible with Vitest.
Check:
- Expression syntax is correct
- Data is not null/undefined
- Property names match exactly
- Operator is supported
Enable debug mode:
const { filtered } = useFilter(data, expression, {
debug: true
});Solutions:
- Enable memoization for repeated queries
- Use pagination for large datasets
- Reduce dataset size with pre-filtering
- Use lazy evaluation for early exit scenarios
- For geospatial queries, use $geoBox before $near
- Optimize datetime queries by filtering date ranges first
// Example: Optimize geospatial + other filters
const nearbyFiltered = filter(data, {
location: { $geoBox: boundingBox } // Fast pre-filter
});
const results = filter(nearbyFiltered, {
location: { $near: { center, maxDistanceMeters: 2000 } },
rating: { $gte: 4.5 }
});Cause: Expression or options recreated on every render.
Solution: Use useMemo:
const expression = useMemo(() => ({
status: { $eq: 'active' }
}), []);- Check Troubleshooting Guide
- Review Examples
- Search GitHub Issues
- Open a new issue
See the Contributing Guide.
Ways to contribute:
- Report bugs
- Suggest features
- Submit pull requests
- Improve documentation
- Share examples
Open a GitHub issue with:
- Library version
- Framework and version
- Minimal reproduction code
- Expected vs actual behavior
Yes! Submit a pull request with:
- Operator implementation
- Tests
- Documentation
- Type definitions