React hooks for building select and combobox components.
npm install @tracksuitdev/use-select* useSelect
* useMultipleSelect
* useCombobox
* useMultipleCombobox
* useAsyncCombobox
* useMultipleAsyncCombobox
``bash`
npm install @tracksuitdev/use-select
or if you use yarn
`bash`
yarn add @tracksuitdev/use-select
▸ useSelect: UseSelectProps): UseSelect
Provides state and callbacks for building select component.
Only required prop are items that can be selected. To control value, provide value and onChange props.
| Name | Type |
| :------ | :------ |
| T | T - Type of items |S
| | S: HTMLElement = HTMLDivElement - Type of select element |D
| | D: HTMLElement = HTMLUListElement- Type of dropdown element |
& Flags| Name | Type | Description |
| :------ | :------ | :------ |
| clear | (e: ReactMouseEvent) => void | Calls onChange with undefined or empty array value in case of multiple selection. Prevents event propagation |dropdownRef
| | RefObjecthandleClick
| | (e: ReactMouseEvent) => void | Toggles isOpen flag, prevents event propagation |handleItemClick
| | (item: T) => void | Calls select if item isn't selected or remove if item is selected |handleKeyDown
| | KeyboardEventHandlerhighlightedIndex
| | number | Index of currently highlighted item, used for keyboard control, ArrowUp key decreases this, while ArrowDown key increases it |isOpen
| | boolean | Indicates whether dropdown is open or not |isSelected
| | (item: T) => boolean | Returns true if item equals value, or in case of multiple selection, if item is part of value array |open
| | () => void | Sets isOpen to true |remove
| | () => void | Calls onChange with value set to undefined |select
| | (item: T) => void | Calls onChange with provided item set as value |selectRef
| | RefObjectsetHighlightedIndex | Ref for combobox element, used internally to allow closing of dropdown on outside click |
| | (index: number) => void | Sets highlightedIndex to provided index |
typescript jsx
const Select = () => {
const [value, setValue] = useState();
const {
selectRef,
open,
handleKeyDown,
isOpen,
handleClick,
dropdownRef,
handleItemClick,
isSelected,
highlightedIndex,
} = useSelect({
items: ["item1", "item2", "item3"],
onChange: value => setValue(value),
value,
}); return (
{/ select /}
{value}
{isOpen && ( // dropdown
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{items.map((item, index) => ( // item
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
``useMultipleSelect
▸ useMultipleSelect(
props: UseMultipleSelectProps): UseMultipleSelectAllows selection of multiple items. Useful for building multiple select component.
$3
| Name | Type |
| :------ | :------ |
|
T | T - Type of items |
| S | S: HTMLElement = HTMLDivElement - Type of select element |
| D | D: HTMLElement = HTMLUListElement- Type of dropdown element |$3
Items & MultiValueControl & Handlers & FlagsSame as useSelect props, only difference are value and onChange props, in this case value is an array and onChange expects array parameter.
$3
UseMultipleSelect: Omit & { remove: (item: T) => void ; removeByIndex: (index: number) => void }Returns a similar object to useSelect, difference is in remove function. Also provides removeByIndex function for removing items according to their index in value array.
| Name | Type | Description |
| :------ | :------ | :------ |
|
remove| (item: T) => void | Calls onChange with value array without the provided item |
| removeByIndex| (index: number) => void | Calls onChange with value array without the item at given index |$3
This example uses basic styling and markup, you can style your components however you want.
Note that you need to assign selectRef and dropdownRef, this is needed so that isOpen is set to false (dropdown is closed) if you click outside select or dropdown element.
If you want your dropdown to scroll to highlighted item when user presses arrow keys make your items direct children of dropdown element.`typescript jsx
const MultipleSelect = () => {
const [value, setValue] = useState();
const {
selectRef,
dropdownRef,
isOpen,
open,
handleKeyDown,
handleClick,
handleItemClick,
isSelected,
highlightedIndex,
} = useMultipleSelect({
items,
onChange: value => setValue(value),
value,
}); return (
{value?.join(", ")}
{isOpen && (
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{items.map((item, index) => (
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
`
useCombobox
▸ useCombobox(
props: UseComboboxProps): UseComboboxHook that returns state and callbacks for controlling combobox component. Updates inputValue according to provided
value (currently selected item). This keeps inputValue and value state in sync whenever an item is selected, or value
was changed by some code.
Internally uses useSelect hook.
$3
| Name | Type |
| :------ | :------ |
|
T | T - Type of items |
| S | S: HTMLElement = HTMLDivElement - Type of select element |
| D | D: HTMLElement = HTMLUListElement- Type of dropdown element |$3
UseComboboxProps: UseSelectProps & ComboboxFunctionsSimilar to useSelect props with added filter and itemToString functions.
filter function is used to filter items according to current input value of combobox. If not provided, defaults to returning items that start with input value.
itemToString function converts item to string so items can be compared to input value.
$3
UseCombobox: UseSelect & UseComboboxReturnValueReturns everything useSelect hook returns + everything contained in UseComboboxReturnValue type.
#### UseComboboxReturnValue
| Name | Type | Description |
| :------ | :------ | :------ |
|
inputRef | RefObject | Ref that needs to be applied to combobox input element |
| inputValue | string | Value of input element |
| items | T[] | Items filtered by filter prop, or in case of async combobox result of fetchItems |
| setInputValue | (value: string) => void | Sets input value to given value |$3
This example uses basic styling and markup, you can style your components however you want.
Note that you need to assign selectRef and dropdownRef, this is needed so that isOpen is set to false (dropdown is closed) if you click outside select or dropdown element.
If you want your dropdown to scroll to highlighted item when user presses arrow keys make your items direct children of dropdown element.
`typescript jsx
const Combobox = () => {
const [value, setValue] = useState();
const {
selectRef,
dropdownRef,
inputRef,
inputValue,
open,
setInputValue,
handleKeyDown,
handleClick,
handleItemClick,
isSelected,
highlightedIndex,
isOpen,
items,
} = useCombobox({
items: comboboxItems,
value,
onChange: value => setValue(value),
itemToString: item => item ?? "",
}); return (
setInputValue(value)} ref={inputRef} />
{isOpen && (
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{items.map((item, index) => (
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
`useMultipleCombobox
▸ useMultipleCombobox(
props: UseMultipleComboboxProps): UseMultipleComboboxProvides state and callbacks for combobox with multiple selection. When value prop changes, inputValue is set to
empty string, thus allowing for selection of new item.
Internally it uses useMultipleSelect hook.
Uses same props as useMultipleSelect + combobox functions (filter and itemToString). Returns same values as useMultipleSelect + values from UseComboboxReturnValue
$3
| Name | Type |
| :------ | :------ |
|
T | T - Type of items |
| S | S: HTMLElement = HTMLDivElement - Type of select element |
| D | D: HTMLElement = HTMLUListElement- Type of dropdown element |$3
UseMultipleComboboxProps: UseMultipleSelectProps & ComboboxFunctions$3
UseMultipleCombobox: UseMultipleSelect & UseComboboxReturnValue$3
This example uses basic styling and markup, you can style your components however you want.
Note that you need to assign selectRef and dropdownRef, this is needed so that isOpen is set to false (dropdown is closed) if you click outside select or dropdown element.
If you want your dropdown to scroll to highlighted item when user presses arrow keys make your items direct children of dropdown element.
`typescript jsx
const MultipleCombobox = () => {
const [value, setValue] = useState();
const {
selectRef,
dropdownRef,
inputRef,
open,
isOpen,
highlightedIndex,
inputValue,
setInputValue,
items,
isSelected,
handleItemClick,
handleClick,
handleKeyDown,
} = useMultipleCombobox({
items: comboboxItems,
itemToString: item => item ?? "",
value,
onChange: setValue,
}); return (
{value?.join(", ")}
setInputValue(value)} ref={inputRef} />
{isOpen && (
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{items.map((item, index) => (
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
`useAsyncCombobox
▸ useAsyncCombobox( props: UseAsyncComboboxProps): UseAsyncComboboxReturns state and callbacks for building combobox component that fetches items asynchronously.
Internally it uses useCombobox hook, but instead of filtering items this hook
calls fetchItems when inputValue changes.
Items returned from this hook are latest result of fetchItems call.
$3
| Name | Type |
| :------ | :------ |
|
T | T - Type of items |
| S | S: HTMLElement = HTMLDivElement - Type of select element |
| D | D: HTMLElement = HTMLUListElement- Type of dropdown element |$3
UseAsyncComboboxProps: { itemToString: ItemToString } & ValueControl & FetchItems & Handlers & FlagsSimilar to useCombobox, but instead of providing items you need to provide fetchItems function that will fetch items asynchronously when input value changes.
$3
UseAsyncCombobox: UseCombobox & LoadingReturns everything useCombobox returns + loading flag that indicates if fetchItems is in progress.
#### Loading
| Name | Type | Description |
| :------ | :------ | :------ |
|
loading | boolean | True if fetchItems has been called but promise hasn't resolved yet. |$3
This example uses basic styling and markup, you can style your components however you want.
Note that you need to assign selectRef and dropdownRef, this is needed so that isOpen is set to false (dropdown is closed) if you click outside select or dropdown element.
If you want your dropdown to scroll to highlighted item when user presses arrow keys make your items direct children of dropdown element.Example uses mock promise that resolves after 100ms timeout for fetchItems. You should use a function that will fetch items from some location and return them.
`typescript jsx
const AsyncCombobox = () => {
const [value, setValue] = useState();
const {
selectRef,
dropdownRef,
inputRef,
inputValue,
open,
setInputValue,
handleKeyDown,
handleClick,
handleItemClick,
isSelected,
highlightedIndex,
isOpen,
items,
loading,
} = useAsyncCombobox({
fetchItems: async _ => {
const promise = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 100);
});
const [result] = await Promise.all([Promise.resolve(comboboxItems), promise]); return result;
},
value,
onChange: value => setValue(value),
itemToString: item => item ?? "",
});
return (
setInputValue(value)} ref={inputRef} />
{isOpen && (
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{loading
? "Loading..."
: items.map((item, index) => (
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
`
useMultipleAsyncCombobox
▸ useMultipleAsyncCombobox(
props: UseMultipleAsyncCombobx): UseMultipleAsyncComboboxSimilar to useMultipleCombobox only this hook fetches new items on inputValue change.
Uses useMultipleCombobox internally.
$3
| Name | Type |
| :------ | :------ |
|
T | T - Type of items |
| S | S: HTMLElement = HTMLDivElement - Type of select element |
| D | D: HTMLElement = HTMLUListElement- Type of dropdown element |$3
UseAsyncComboboxProps: { itemToString: ItemToString } & MultiValueControl & FetchItems & Handlers & Flags$3
UseMultipleAsyncCombobox: UseMultipleCombobox & LoadingReturns everything useMultipleCombobox returns + loading flag.
$3
This example uses basic styling and markup, you can style your components however you want.
Note that you need to assign selectRef and dropdownRef, this is needed so that isOpen is set to false (dropdown is closed) if you click outside select or dropdown element.
If you want your dropdown to scroll to highlighted item when user presses arrow keys make your items direct children of dropdown element.Example uses mock promise that resolves after 100ms timeout for fetchItems. You should use a function that will fetch items from some location and return them.
`typescript jsx
const MultipleAsyncCombobox = () => {
const [value, setValue] = useState();
const {
selectRef,
dropdownRef,
inputRef,
inputValue,
open,
setInputValue,
handleKeyDown,
handleClick,
handleItemClick,
isSelected,
highlightedIndex,
isOpen,
items,
loading,
} = useMultipleAsyncCombobox({
fetchItems: async _ => {
const promise = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 100);
});
const [result] = await Promise.all([Promise.resolve(comboboxItems), promise]); return result;
},
value,
onChange: value => setValue(value),
itemToString: item => item ?? "",
});
return (
{value?.join(", ")}
setInputValue(value)} ref={inputRef} />
{isOpen && (
ref={dropdownRef}
tabIndex={0}
onKeyDown={handleKeyDown}
style={{ width: "400px", backgroundColor: "grey" }}>
{loading
? "Loading..."
: items.map((item, index) => (
key={item}
onClick={() => handleItemClick(item)}
style={{
color: isSelected(item) ? "blue" : "black",
backgroundColor: highlightedIndex === index ? "green" : "grey",
cursor: "pointer",
}}>
{item}
))}
)}
);
};
`
Common Types
$3
#### Type parameters
| Name | Description |
| :------ | :------ |
|
T | Type of items |#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
|
fetchItems | (query: string) => Promise | Fetch items asynchronously |___
$3
| Name | Type | Description |
| :------ | :------ | :------ |
|
clearable? | boolean | If true value can be set to undefined for value, and for array value can be set to an empty array. Note that for array value case it is still possible to set value to an empty array by calling remove or removeByIndex on every selected item. |
| disabled? | boolean | If true open function does nothing, same as readOnly, provided as separate prop for convenience |
| readOnly? | boolean | If true open function does nothing, same as disabled, provided as separate prop for convenience |___
$3
| Name | Type | Description |
| :------ | :------ | :------ |
|
onClose? | () => void | This function is called when isOpen is set to false |
| onOpen? | () => void | This function is called when isOpen is set to true |___
$3
#### Type parameters
| Name | Description |
| :------ | :------ |
|
T | Type of items || Name | Type | Description |
| :------ | :------ | :------ |
|
items | T[] | Options that can be selected |___
$3
onChange handler and value type for hooks where multiple selection is allowed
#### Type parameters
| Name | Description |
| :------ | :------ |
|
T | Type of items |#### Type declaration
| Name | Type |
| :------ | :------ |
|
onChange? | (value?: T[]) => void |
| value? | T[] |___
$3
onChange handler and value type for hooks where only single selection is allowed
#### Type parameters
| Name | Description |
| :------ | :------ |
|
T | Type of items |#### Type declaration
| Name | Type |
| :------ | :------ |
|
onChange? | (value?: T) => void |
| value? | T |___
$3
Filter and itemToString props for combobox.#### Type parameters
| Name | Description |
| :------ | :------ |
|
T | Type of items |#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
|
filter? | (items: T[], query: string, itemToString: ItemToString) => T[] | Provided items are equal to items prop, query is equal to current input value of combobox, and itemToString is equal to itemToString prop. Should return filtered items. If not provided, defaults to items.filter(item => itemToString(item).toLowerCase().startsWith(query.toLowerCase())) |
| itemToString | ItemToString | Function that converts item to string. Since items can be of any type, to compare them we need to have a way of converting them to string. |$3
Function that converts item to string. Since items can be of any type, to compare them we need to have a way of converting them to string.
T - type of item(
item?: T) => stringExamples
To run examples run
yarn start` inside example directory--------
Made with tsdx