Development of Product Catalog with Filters in Mobile Application
Catalog with filters — not "screen with list and checkboxes". This is managing state of several independent dimensions (category, price, brand, availability, rating) that must apply instantly, persist between sessions and not break deep links. This is where most teams stumble — not on layout, but on state architecture.
Typical issues
Filter state loss. User selected three filters, navigated to product card, returned — catalog reset. Classic. In React Native happens when filters stored in local state of screen component, and Navigator unmounts it on transition. Solution: extract filters to global store — Zustand, Redux Toolkit or Recoil. On Flutter — InheritedWidget or Riverpod's StateNotifierProvider, living above Navigator in widget tree.
Performance with large catalog. FlatList in React Native starts jerking at 500+ products with images if not configured with getItemLayout (fixed cell height eliminates recalculation), windowSize not set (default 21 — this is 10 screens above and below viewport, for heavy cells lower to 5–7), and FlashList from Shopify not connected instead of standard FlatList. FlashList uses cell reuse at JS level, giving real performance boost on mid-range Android.
On Android with Jetpack Compose — LazyVerticalGrid with key by product id and rememberLazyGridState to restore scroll position. Images via Coil with AsyncImage and memoryCachePolicy = CachePolicy.ENABLED.
Filter implementation. Most labor-intensive — not rendering filters, but applying them. If server-side filtering, each filter change must debounce (300–500 ms), cancel previous request (AbortController in RN, CancellationToken or Job.cancel() in Android coroutines) and show skeleton instead of empty screen during loading.
For client-side filtering (whole catalog preloaded) use memoization: useMemo / useCallback in RN, remember { derivedStateOf {} } in Compose. Without this each rerender recalculates 500 products by all active filters.
Filter architecture
FilterState {
categories: Set<string>
priceRange: { min: number, max: number }
brands: Set<string>
inStock: boolean
sortBy: 'price_asc' | 'price_desc' | 'rating' | 'new'
}
Such structure easily serializes to URL query params for deep linking and AsyncStorage/SharedPreferences for persistence. Deep link like myapp://catalog?category=shoes&brand=nike&price_max=5000 fully restores filter state on open.
Components and UX
Implement filters in bottom sheet (not full-screen modal, so user sees catalog context). In React Native — @gorhom/bottom-sheet with snapPoints. Show badge with number of active filters on Filters button.
Active filter chips above product list with ability to delete each — mandatory element. Without them user doesn't understand why sees 3 products from 500.
Sort — separate ActionSheet or segmented control, don't mix with filters.
Case from practice: clothing e-commerce, React Native + Redux Toolkit. Filter by size + color + brand. After applying filters app hung for 800 ms — recalculation happened synchronously in main thread on each checkbox tap. Reworked: filter application via createSelector with reselect, debounce 200 ms on any parameter change, computation extracted to useTransition (React 18) — interface stopped blocking.
Grid vs list
Provide both modes with choice saved in AsyncStorage. In grid — numColumns={2} with dynamic card width calculation via Dimensions.get('window').width. On tablet auto-switch to 3–4 columns.
What's included
- Catalog screen with LazyList/FlatList/LazyVerticalGrid
- Bottom sheet with filters: categories, price range (RangeSlider), multi-select, toggles
- Active filter chips with ability to clear
- Sort with choice persistence
- Grid/list view toggle
- API integration: debounce, request cancellation, skeleton loading
- Pagination or infinite scroll in catalog
- Filter state persistence between sessions
- Deep linking to catalog with preset filters
Timeline
3–5 working days — depends on number of filter types, server or client filtering and product card complexity. Cost calculated individually after analyzing requirements and API structure.







