A react hook to filter data based on multiple search queries.
- 🗃️ Support for various data types
- 📈 Range queries for numbers and dates
- 📜 Search suggestions
- 📊 Support for categorized data
- ✨ Headless, bring your own UI
- 🌐 TypeScript support
- String
- Can be case-sensitive or case-insensitive (default).
- Exact matches can be done by enclosing the query in double quotes.
- Can come with search suggestions, which is the aggregated possible values for a specific field.
- Boolean
- Support for various truthy and falsy queries (
true,yes,1, etc.) or provide your own.
- Support for various truthy and falsy queries (
- Number
- Support for range queries with operators:
>,>=,<,<=, and!=.
- Support for range queries with operators:
- Date
- Support for range queries with operators like numbers.
- Time and timezone are ignored.
Other data types will be treated as strings.
To see the hook in action, you can check the Storybook demo.
npm:
npm i react-multi-searchyarn:
yarn add react-multi-searchimport { useState } from 'react';
import { useMultiSearch } from 'react-multi-search';
const MyComponent = () => {
const initialData = [
{ name: 'John Doe', age: 20, gender: 'Male' },
{ name: 'Jane Doe', age: 25, gender: 'Female' },
// ...
];
const [filteredData, setFilteredData] = useState([]);
const {
states: {
searchString,
searchField,
searchSuggestions,
searchQueries,
isMenuOpen,
shownMenu,
},
actions: {
clearInput,
addSearchQuery,
deleteSearchQuery,
deleteAllSearchQueries,
onMenuKeyDown,
onSearchFieldSelect,
onAllSearchFieldSelect,
onSearchSuggestionSelect,
openMenu,
},
inputProps,
anchorRef,
listRef,
} = useMultiSearch({
initialData,
setFilteredData,
fields: [
{ value: 'name', label: 'Name' },
{ value: 'age', label: 'Age' },
{ value: 'gender', label: 'Gender', showSearchSuggestions: true },
],
});
return (
<>
{/* search bar ------------------------------------------------------- */}
<div ref={anchorRef}>
<button onClick={openMenu}>{searchField.label || 'All'}</button>
<input {...inputProps} />
<button onClick={clearInput}>Clear</button>
<button onClick={addSearchQuery}>Add</button>
</div>
{/* search queries --------------------------------------------------- */}
<ul
style={{
listStyle: 'none',
padding: 0,
}}
>
{searchQueries.map((query, index) => (
<li key={index}>
<span>
{query.field === '_default'
? query.value
: `${query.fieldLabel}: ${query.value}`}
</span>
<button onClick={() => deleteSearchQuery(index)}>Remove</button>
</li>
))}
</ul>
<button onClick={deleteAllSearchQueries}>Clear all queries</button>
{/* dropdown menu ---------------------------------------------------- */}
{isMenuOpen && (
<div>
<ul ref={listRef} onKeyDown={onMenuKeyDown}>
{shownMenu === 'fields' && (
<>
<li>Search by:</li>
<li>
<button
onClick={onAllSearchFieldSelect}
disabled={searchField.value === '_default'}
>
All
</button>
</li>
{...props.fields.map((field) => (
<li key={field.value as string}>
<button
onClick={() => onSearchFieldSelect(field)}
disabled={searchField.value === field.value}
>
{field.label}
</button>
</li>
))}
</>
)}
{shownMenu === 'searchSuggestions' && (
<>
<li>Search suggestions:</li>
{searchSuggestions?.map(
(value) => (
<li key={value}>
<button onClick={() => onSearchSuggestionSelect(value)}>
{value}
</button>
</li>
)
) ?? <li>No suggestions</li>}
</>
)}
</ul>
</div>
)}
{/* ... */}
</>
);
};See the demo code for a more comprehensive example.
-
initialData(required)T[]- The initial data to filter. This should be an array of objects without categorization. If you want to categorize your data, use the separate
categorizerfunction.
-
setFilteredData(required)Dispatch<SetStateAction<T[]>> | Dispatch<SetStateAction<Record<string, T[]>>>- setState function to update the filtered data.
- Since this is a headless hook, the filtered data should be managed by the consuming component for reusability.
-
fields(required)(FieldWithSuggestions<T> | FieldWithoutSuggestions<T>)[]- The fields to search in. Each item in the array can be of type
FieldWithSuggestions<T>where all keys should have the value of typestringorboolean, orFieldWithoutSuggestions<T>for other data types.
-
categorizer(data: T[]) => Record<string, (T | Record<string, unknown>)[]>- Function to categorize and group the filtered data.
- This is separated from
initialDatato optimize performance when filtering.
-
showEmptyCategoriesboolean- Defaults to
false. - Show categories that have no items.
- This is only used when
categorizeris provided.
-
shouldInitializeboolean- Defaults to
true. - Control whether the hook should initialize or not.
- This is useful when you want to delay the initialization of the hook until the data is ready.
-
globalSearchReplacementkeyof T- The field to use for global search when no specific field is selected.
- This is used when the search field is set to
'_default'.
-
overrideExistingQueriesWithSameFieldboolean- Override existing queries with the same field.
-
caseSensitiveboolean- Defaults to
false. - Whether the comparison should be case-sensitive or not.
-
trueLabelstring- Defaults to
Yes. - The label for
truevalue on search suggestions.
-
falseLabelstring- Defaults to
No. - The label for
falsevalue on search suggestions.
-
truthyValuesstring[]- Defaults to
['true', '1', 'on', 'yes', 'y', 't', '✓'] - A list of truthy values to match against boolean values.
-
falsyValuesstring[]- Defaults to
['false', '0', 'off', 'no', 'n', 'f', 'x'] - A list of falsy values to match against boolean values.
-
fields[number].value(required){ [K in keyof T]: T[K] extends string | boolean ? K : never; }[keyof T]forFieldWithSuggestions<T>{ [K in keyof T]: T[K] extends string | boolean ? never : K; }[keyof T]forFieldWithoutSuggestions<T>- The field to search.
-
fields[number].label(required)string- The label for the field.
-
fields[number].showSearchSuggestionsboolean(default:false)- Show search suggestions for this field.
- This only applies to
FieldWithoutSuggestions
-
fields[number].strictSuggestionsboolean(default:false)- Enclose suggestions in double quotes to treat as exact match.
- This only applies to
FieldWithoutSuggestions
-
fields[number].searchSuggestionsT[FieldWithSuggestions<T>['value']][]- The search suggestions for this field.
- If provided, it will be used instead of computing the suggestions.
-
states- Usable states for the search filter.-
searchStringstring- The current search string.
-
searchFieldstring[]- Selected field to search.
-
searchSuggestionsFieldWithSuggestions<T> | FieldWithoutSuggestions<T> | { value: '_default'; label: ''; }- Search suggestions for the selected field.
-
SearchQuery<T>[]- Current search queries.
-
isMenuOpenboolean- Dropdown menu open state.
-
shownMenu'fields' | 'searchSuggestions'- The type of dropdown menu currently shown.
-
isFilteredboolean- Whether the data is filtered or not.
-
isInitializedboolean- Whether the hook is initialized or not.
-
-
actions- Actions to interact with the search filter.-
clearInput() => void- Clear the search input.
-
addSearchQuery() => void- Trigger to add the current search string and field as a search query.
-
deleteSearchQuery(idx: number) => void- Delete a search query by index.
-
deleteAllSearchQueries() => void- Delete all search queries.
-
onMenuKeyDown(e: KeyboardEvent<HTMLUListElement>) => void- Event handler to handle key down events on the menu.
-
onSearchFieldSelect(field: FieldWithSuggestions<T> | FieldWithoutSuggestions<T> | { value: '_default'; label: ''; }) => void- Event handler to handle field selection.
-
onAllSearchFieldSelect() => void- Event handler to handle "All" field selection.
-
onSearchSuggestionSelect(value: string) => void- Event handler to handle search suggestion selection.
-
openMenu() => void- Open the dropdown menu.
-
closeMenu() => void- Close the dropdown menu.
-
-
inputProps- Props passed to the input element (search bar).-
onChange(e: ChangeEvent<HTMLInputElement>) => void- Event handler to handle input change.
-
onKeyDown(e: KeyboardEvent<HTMLInputElement>) => void- Event handler to handle key down events on the input.
- Includes handling various keys:
Enter,Escape,Backspace,ArrowDown, and field selection (:).
-
onPaste(e: ClipboardEvent<HTMLInputElement>) => void- Event handler to handle paste events on the input.
- Primarily for separating search field and string.
-
onFocus() => void- Event handler to handle focus events on the input.
- Used for opening the dropdown menu.
-
onBlur() => void- Event handler to handle blur events on the input.
-
valuestring- Same as
searchString.
-
refRefObject<HTMLInputElement>- Ref forwarded to the input element.
-
-
anchorRef- Ref forwarded to the anchor element (search bar wrapper). -
listRef- Ref forwarded to the dropdown menu list element (ul).
-
fieldkeyof T | '_default'- The field to search.
-
fieldLabelstring- The label to display.
-
valuestring- Query string to match.
Contributions are welcome! Feel free to open an issue or submit a pull request.
Before submitting a pull request, please make sure to test your changes and update the documentation if necessary.
This project is licensed under the MIT License