Overview
CryptoTracker provides powerful filtering capabilities that allow users to search for cryptocurrencies by name or symbol, and filter by price range. The filtering system uses React Context for state management and implements debounced search for optimal performance.
FilterContext Architecture
The filtering system is built around the FilterContext, which provides centralized filter state management across the application.
Filter Interface
export interface Filter {
text : string ; // Search text for name/symbol
minPrice : number | null ; // Minimum price filter
maxPrice : number | null ; // Maximum price filter
}
Context Setup
type FilterContextType = {
filter : Filter ;
setFilter : React . Dispatch < React . SetStateAction < Filter >>;
};
const FilterContext = createContext < FilterContextType | undefined >( undefined );
export const FilterProvider = ({ children } : { children : ReactNode }) => {
const [ filter , setFilter ] = useState < Filter >({
text: '' ,
minPrice: null ,
maxPrice: null ,
});
return (
< FilterContext.Provider value = { { filter , setFilter } } >
{ children }
</ FilterContext.Provider >
);
};
useFilter Hook
The custom useFilter hook provides type-safe access to the filter context:
export const useFilter = () => {
const ctx = useContext ( FilterContext );
if ( ! ctx ) {
throw new Error ( 'useFilter debe usarse dentro de FilterProvider' );
}
return ctx ;
};
The hook throws an error if used outside of FilterProvider, ensuring proper context usage throughout the app.
SearchBar Component
The SearchBar component provides the UI for entering filter criteria with three input fields.
Implementation
const SearchBar = ({ onSearch } : Props ) => {
const [ text , setText ] = useState ( '' );
const [ minPrice , setMinPrice ] = useState < number | null >( null );
const [ maxPrice , setMaxPrice ] = useState < number | null >( null );
useEffect (() => {
const timeout = setTimeout (() => {
onSearch ({
text ,
minPrice: minPrice ? minPrice : null ,
maxPrice: maxPrice ? maxPrice : null ,
});
}, 300 );
return () => clearTimeout ( timeout );
}, [ text , minPrice , maxPrice ]);
return (
< View style = { styles . container } >
< TextInput
placeholder = "Buscar criptomoneda..."
value = { text }
onChangeText = { setText }
style = { styles . input }
/>
< TextInput
placeholder = "Precio mínimo"
value = { minPrice ?. toString () ?? '' }
onChangeText = { ( val ) => {
const parsed = parseFloat ( val );
setMinPrice ( isNaN ( parsed ) ? null : parsed );
} }
style = { styles . input }
keyboardType = "numeric"
/>
< TextInput
placeholder = "Precio máximo"
value = { maxPrice ?. toString () ?? '' }
onChangeText = { ( val ) => {
const parsed = parseFloat ( val );
setMaxPrice ( isNaN ( parsed ) ? null : parsed );
} }
style = { styles . input }
keyboardType = "numeric"
/>
</ View >
);
};
Debounced Search
The SearchBar implements a 300ms debounce delay to prevent excessive re-renders and API calls while users are typing.
The useEffect hook sets up a timeout that triggers the onSearch callback only after the user stops typing for 300 milliseconds:
useEffect (() => {
const timeout = setTimeout (() => {
onSearch ({ text , minPrice , maxPrice });
}, 300 );
return () => clearTimeout ( timeout ); // Cleanup on unmount or value change
}, [ text , minPrice , maxPrice ]);
const styles = StyleSheet . create ({
container: {
backgroundColor: '#fff' ,
padding: 12 ,
borderRadius: 12 ,
marginBottom: 16 ,
shadowColor: '#000' ,
shadowOffset: { width: 0 , height: 2 },
shadowOpacity: 0.1 ,
shadowRadius: 6 ,
elevation: 4 ,
},
input: {
fontSize: 16 ,
color: '#333' ,
borderWidth: 1 ,
marginBottom: 5 ,
marginTop: 5 ,
borderColor: '#ddd' ,
paddingHorizontal: 12 ,
paddingVertical: 8 ,
borderRadius: 8 ,
},
});
Filter Logic in HomeScreen
The HomeScreen component applies the filter criteria to the cryptocurrency data:
const HomeScreen = () => {
const { data , loading , error } = useCryptoData ();
const { filter } = useFilter ();
// Apply filters to cryptocurrency data
const filtered = data . filter (( crypto ) => {
// Text search: match name OR symbol (case-insensitive)
const nameMatch = crypto . name
. toLowerCase ()
. includes ( filter . text . toLowerCase ());
const symbolMatch = crypto . symbol
. toLowerCase ()
. includes ( filter . text . toLowerCase ());
// Price range filters
const minOk = filter . minPrice === null ||
Number ( crypto . price_usd ) >= filter . minPrice ;
const maxOk = filter . maxPrice === null ||
Number ( crypto . price_usd ) <= filter . maxPrice ;
// All conditions must be satisfied
return ( nameMatch || symbolMatch ) && minOk && maxOk ;
});
return (
< ScrollView >
{ filtered . map (( crypto ) => (
< CryptoCard key = { crypto . id } crypto = { crypto } />
)) }
</ ScrollView >
);
};
How Filtering Works
The filter logic uses a multi-stage approach:
Text Matching
Checks if the search text appears in either the cryptocurrency name or symbol (case-insensitive). const nameMatch = crypto . name . toLowerCase (). includes ( filter . text . toLowerCase ());
const symbolMatch = crypto . symbol . toLowerCase (). includes ( filter . text . toLowerCase ());
Minimum Price Check
If a minimum price is set, ensures the cryptocurrency price is greater than or equal to it. const minOk = filter . minPrice === null || Number ( crypto . price_usd ) >= filter . minPrice ;
Maximum Price Check
If a maximum price is set, ensures the cryptocurrency price is less than or equal to it. const maxOk = filter . maxPrice === null || Number ( crypto . price_usd ) <= filter . maxPrice ;
Combined Logic
A cryptocurrency is shown if it matches the text (name OR symbol) AND satisfies both price constraints. return ( nameMatch || symbolMatch ) && minOk && maxOk ;
Filter Examples
Search by Name
Search by Symbol
Price Range Filter
Combined Filters
// User types "bit" in search box
filter = { text: 'bit' , minPrice: null , maxPrice: null }
// Results: Bitcoin, BitTorrent, Bitshares, etc.
Debouncing
The 300ms debounce prevents excessive filtering operations while typing:
Without debounce : Filter runs on every keystroke (typing “bitcoin” = 7 operations)
With debounce : Filter runs once after user stops typing (1 operation)
Null Handling
Price filters use null checks to skip unnecessary comparisons when no price range is set:
const minOk = filter . minPrice === null || Number ( crypto . price_usd ) >= filter . minPrice ;
If minPrice is null, the expression short-circuits and returns true without evaluating the price comparison.
Case-Insensitive Search
Both search terms and cryptocurrency names/symbols are converted to lowercase once per iteration:
const lowerText = filter . text . toLowerCase ();
const nameMatch = crypto . name . toLowerCase (). includes ( lowerText );
Key Features
Real-time Filtering : Results update as users type (with debouncing)
Multi-field Search : Matches both name and symbol fields
Price Range : Filter by minimum and/or maximum price
Context-based State : Centralized filter state accessible throughout the app
Type Safety : TypeScript interfaces ensure correct filter structure
Performance Optimized : Debounced inputs and efficient filter logic
Flexible Filters : Each filter criterion is optional and can be used independently