A comprehensive cascading select component for React applications that enables hierarchical option selection through a dropdown interface
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advanced search capabilities with filtering, custom rendering, sorting, and result limiting. Enables users to quickly find options in large hierarchical datasets through text input.
Configure search behavior with filtering, rendering, and sorting options.
interface ShowSearchType<
OptionType extends DefaultOptionType = DefaultOptionType,
ValueField extends keyof OptionType = keyof OptionType
> {
/** Custom filter function for search matching */
filter?: (
inputValue: string,
options: OptionType[],
fieldNames: FieldNames<OptionType, ValueField>
) => boolean;
/** Custom rendering for search result items */
render?: (
inputValue: string,
path: OptionType[],
prefixCls: string,
fieldNames: FieldNames<OptionType, ValueField>
) => React.ReactNode;
/** Custom sorting function for search results */
sort?: (
a: OptionType[],
b: OptionType[],
inputValue: string,
fieldNames: FieldNames<OptionType, ValueField>
) => number;
/** Whether search dropdown should match input width */
matchInputWidth?: boolean;
/** Maximum number of search results to display (false for unlimited) */
limit?: number | false;
}Simple search enablement and control.
interface BasicSearchProps<
OptionType extends DefaultOptionType,
ValueField extends keyof OptionType
> {
/** Enable search with boolean or advanced configuration */
showSearch?: boolean | ShowSearchType<OptionType, ValueField>;
/** Controlled search input value */
searchValue?: string;
/** Search input change callback */
onSearch?: (value: string) => void;
/** Clear search value when selecting an item (multiple mode only) */
autoClearSearchValue?: boolean;
}Custom filtering logic for search matching.
/**
* Custom filter function for search matching
* @param inputValue - Current search input text
* @param options - Array of options in the current path
* @param fieldNames - Field name mapping configuration
* @returns true if the path should be included in search results
*/
type SearchFilter<
OptionType extends DefaultOptionType,
ValueField extends keyof OptionType
> = (
inputValue: string,
options: OptionType[],
fieldNames: FieldNames<OptionType, ValueField>
) => boolean;Custom rendering for search result items.
/**
* Custom rendering for search result items
* @param inputValue - Current search input text
* @param path - Array of options representing the path to this result
* @param prefixCls - CSS class prefix for styling
* @param fieldNames - Field name mapping configuration
* @returns Custom rendered content for the search result
*/
type SearchRender<
OptionType extends DefaultOptionType,
ValueField extends keyof OptionType
> = (
inputValue: string,
path: OptionType[],
prefixCls: string,
fieldNames: FieldNames<OptionType, ValueField>
) => React.ReactNode;Custom sorting logic for search results.
/**
* Custom sorting function for search results
* @param a - First option path for comparison
* @param b - Second option path for comparison
* @param inputValue - Current search input text
* @param fieldNames - Field name mapping configuration
* @returns Negative, zero, or positive number for sorting order
*/
type SearchSort<
OptionType extends DefaultOptionType,
ValueField extends keyof OptionType
> = (
a: OptionType[],
b: OptionType[],
inputValue: string,
fieldNames: FieldNames<OptionType, ValueField>
) => number;import React from 'react';
import Cascader from 'rc-cascader';
const options = [
{
label: 'Zhejiang',
value: 'zhejiang',
children: [
{
label: 'Hangzhou',
value: 'hangzhou',
children: [
{ label: 'West Lake', value: 'xihu' },
{ label: 'Xiaoshan', value: 'xiaoshan' }
]
},
{ label: 'Ningbo', value: 'ningbo' }
]
},
{
label: 'Jiangsu',
value: 'jiangsu',
children: [
{ label: 'Nanjing', value: 'nanjing' },
{ label: 'Suzhou', value: 'suzhou' }
]
}
];
const BasicSearchExample = () => {
return (
<Cascader
options={options}
showSearch
placeholder="Search locations"
style={{ width: 300 }}
/>
);
};const CustomFilterExample = () => {
return (
<Cascader
options={options}
showSearch={{
filter: (inputValue, path, fieldNames) => {
// Search in any level of the path
return path.some(option =>
option[fieldNames.label]
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}
}}
placeholder="Custom filter search"
/>
);
};const CustomRenderExample = () => {
return (
<Cascader
options={options}
showSearch={{
render: (inputValue, path, prefixCls, fieldNames) => {
const labels = path.map(option => option[fieldNames.label]);
const fullPath = labels.join(' / ');
// Highlight matched text
const regex = new RegExp(`(${inputValue})`, 'gi');
const highlightedPath = fullPath.replace(
regex,
'<mark>$1</mark>'
);
return (
<div
className={`${prefixCls}-menu-item-search-result`}
dangerouslySetInnerHTML={{ __html: highlightedPath }}
/>
);
}
}}
placeholder="Custom render search"
/>
);
};const SortedLimitedSearchExample = () => {
return (
<Cascader
options={options}
showSearch={{
filter: (inputValue, path) =>
path.some(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
),
sort: (a, b, inputValue) => {
// Sort by relevance - exact matches first
const aLabel = a[a.length - 1].label.toLowerCase();
const bLabel = b[b.length - 1].label.toLowerCase();
const input = inputValue.toLowerCase();
const aExact = aLabel.startsWith(input) ? 0 : 1;
const bExact = bLabel.startsWith(input) ? 0 : 1;
if (aExact !== bExact) return aExact - bExact;
// Then sort alphabetically
return aLabel.localeCompare(bLabel);
},
limit: 50,
matchInputWidth: true
}}
placeholder="Sorted and limited search"
/>
);
};const ControlledSearchExample = () => {
const [searchValue, setSearchValue] = useState('');
const [value, setValue] = useState([]);
return (
<div>
<Cascader
options={options}
showSearch={{
filter: (inputValue, path) =>
path.some(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
)
}}
searchValue={searchValue}
onSearch={setSearchValue}
value={value}
onChange={setValue}
placeholder="Controlled search"
/>
<p>Current search: {searchValue}</p>
<p>Selected value: {JSON.stringify(value)}</p>
</div>
);
};const AutoClearSearchExample = () => {
return (
<Cascader
checkable
options={options}
showSearch
autoClearSearchValue={true}
placeholder="Search clears on selection"
style={{ width: 300 }}
/>
);
};const products = [
{
name: 'Electronics',
id: 'electronics',
description: 'Electronic devices and gadgets',
children: [
{
name: 'Laptops',
id: 'laptops',
description: 'Portable computers',
children: [
{ name: 'Gaming Laptop', id: 'gaming-laptop', description: 'High performance gaming' },
{ name: 'Business Laptop', id: 'business-laptop', description: 'Professional computing' }
]
}
]
}
];
const MultiFieldSearchExample = () => {
return (
<Cascader
options={products}
fieldNames={{
label: 'name',
value: 'id',
children: 'children'
}}
showSearch={{
filter: (inputValue, path) => {
const input = inputValue.toLowerCase();
return path.some(option =>
option.name.toLowerCase().includes(input) ||
option.description?.toLowerCase().includes(input)
);
},
render: (inputValue, path) => {
const item = path[path.length - 1];
const pathNames = path.map(p => p.name).join(' / ');
return (
<div>
<div style={{ fontWeight: 'bold' }}>{pathNames}</div>
{item.description && (
<div style={{ fontSize: '12px', color: '#666' }}>
{item.description}
</div>
)}
</div>
);
}
}}
placeholder="Search products and descriptions"
style={{ width: 400 }}
/>
);
};import { useMemo } from 'react';
const OptimizedSearchExample = () => {
// Memoize search configuration to prevent re-renders
const searchConfig = useMemo(() => ({
filter: (inputValue, path, fieldNames) => {
if (!inputValue) return false;
const input = inputValue.toLowerCase();
return path.some(option => {
const label = option[fieldNames.label];
return typeof label === 'string' &&
label.toLowerCase().includes(input);
});
},
limit: 100,
matchInputWidth: false
}), []);
return (
<Cascader
options={options}
showSearch={searchConfig}
placeholder="Optimized search"
/>
);
};Install with Tessl CLI
npx tessl i tessl/npm-rc-cascader