useTypeahead
Provides a matching callback that can be used to focus an item as
the user types, often used in tandem with
useListNavigation()
useListNavigation()
.
import {useTypeahead} from '@floating-ui/react';
import {useTypeahead} from '@floating-ui/react';
See FloatingList
for creating composable
children API components.
Usage
This hook is an interaction hook that returns event handler props.
To use it, pass it the context
context
object returned from
useFloating()
useFloating()
, and then feed its result into the
useInteractions()
useInteractions()
array. The returned prop getters are
then spread onto the elements for rendering.
useListNavigation()
useListNavigation()
is responsible for synchronizing the
index for focus.
function App() {
const [activeIndex, setActiveIndex] = useState(null);
const {refs, floatingStyles, context} = useFloating({
open: true,
});
const items = ['one', 'two', 'three'];
const listRef = useRef(items);
const typeahead = useTypeahead(context, {
listRef,
activeIndex,
onMatch: setActiveIndex,
});
const {getReferenceProps, getFloatingProps, getItemProps} =
useInteractions([typeahead]);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
Reference element
</div>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
{items.map((item, index) => (
<div
key={item}
// Make these elements focusable using a roving tabIndex.
tabIndex={activeIndex === index ? 0 : -1}
{...getItemProps()}
>
{item}
</div>
))}
</div>
</>
);
}
function App() {
const [activeIndex, setActiveIndex] = useState(null);
const {refs, floatingStyles, context} = useFloating({
open: true,
});
const items = ['one', 'two', 'three'];
const listRef = useRef(items);
const typeahead = useTypeahead(context, {
listRef,
activeIndex,
onMatch: setActiveIndex,
});
const {getReferenceProps, getFloatingProps, getItemProps} =
useInteractions([typeahead]);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
Reference element
</div>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
{items.map((item, index) => (
<div
key={item}
// Make these elements focusable using a roving tabIndex.
tabIndex={activeIndex === index ? 0 : -1}
{...getItemProps()}
>
{item}
</div>
))}
</div>
</>
);
}
Props
interface Props {
listRef: React.MutableRefObject<Array<string | null>>;
activeIndex: number | null;
onMatch?: (index: number) => void;
enabled?: boolean;
findMatch?:
| null
| ((
list: Array<string | null>,
typedString: string
) => string | null | undefined);
resetMs?: number;
ignoreKeys?: Array<string>;
selectedIndex?: number | null;
onTypingChange?: (isTyping) => void;
}
interface Props {
listRef: React.MutableRefObject<Array<string | null>>;
activeIndex: number | null;
onMatch?: (index: number) => void;
enabled?: boolean;
findMatch?:
| null
| ((
list: Array<string | null>,
typedString: string
) => string | null | undefined);
resetMs?: number;
ignoreKeys?: Array<string>;
selectedIndex?: number | null;
onTypingChange?: (isTyping) => void;
}
listRef
Required
default: empty list
A ref which contains an array of strings whose indices match the HTML elements of the list.
const listRef = useRef(['one', 'two', 'three']);
useTypeahead(context, {
listRef,
});
const listRef = useRef(['one', 'two', 'three']);
useTypeahead(context, {
listRef,
});
You can derive these strings when assigning the node if the strings are not available up front:
// Array<HTMLElement | null> for `useListNavigation`
const listItemsRef = useRef([]);
// Array<string | null> for `useTypeahead`
const listContentRef = useRef([]);
// Array<HTMLElement | null> for `useListNavigation`
const listItemsRef = useRef([]);
// Array<string | null> for `useTypeahead`
const listContentRef = useRef([]);
<li
ref={(node) => {
listItemsRef.current[index] = node;
listContentRef.current[index] = node?.textContent ?? null;
}}
/>
<li
ref={(node) => {
listItemsRef.current[index] = node;
listContentRef.current[index] = node?.textContent ?? null;
}}
/>
Disabled items can be represented by null
null
values in the
array at the relevant index, and will be skipped.
activeIndex
Required
default: null
null
The currently active index. This specifies where the typeahead starts.
const [activeIndex, setActiveIndex] = useState(null);
useTypeahead(context, {
activeIndex,
});
const [activeIndex, setActiveIndex] = useState(null);
useTypeahead(context, {
activeIndex,
});
onMatch
default: no-op
Callback invoked with the matching index if found as the user types.
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(null);
useTypeahead(context, {
onMatch: isOpen ? setActiveIndex : setSelectedIndex,
});
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(null);
useTypeahead(context, {
onMatch: isOpen ? setActiveIndex : setSelectedIndex,
});
enabled
default: true
true
Conditionally enable/disable the hook.
useTypeahead(context, {
enabled: false,
});
useTypeahead(context, {
enabled: false,
});
findMatch
default: lowercase finder
If you’d like to implement custom finding logic (for example fuzzy search), you can use this callback.
useTypeahead(context, {
findMatch: (list, typedString) =>
list.find(
(itemString) =>
itemString?.toLowerCase().indexOf(typedString) === 0
),
});
useTypeahead(context, {
findMatch: (list, typedString) =>
list.find(
(itemString) =>
itemString?.toLowerCase().indexOf(typedString) === 0
),
});
resetMs
default: 750
750
Debounce timeout which will reset the transient string as the user types.
useTypeahead(context, {
resetMs: 500,
});
useTypeahead(context, {
resetMs: 500,
});
ignoreKeys
default: []
[]
Optional keys to ignore.
useTypeahead(context, {
ignoreKeys: ['I', 'G', 'N', 'O', 'R', 'E'],
});
useTypeahead(context, {
ignoreKeys: ['I', 'G', 'N', 'O', 'R', 'E'],
});
selectedIndex
default: null
null
The currently selected index, if available.
const [selectedIndex, setSelectedIndex] = useState(null);
useTypeahead(context, {
selectedIndex,
});
const [selectedIndex, setSelectedIndex] = useState(null);
useTypeahead(context, {
selectedIndex,
});
onTypingChange
default: no-op
Callback invoked with the typing state as the user types.
useTypeahead(context, {
onTypingChange(isTyping) {
// ...
},
});
useTypeahead(context, {
onTypingChange(isTyping) {
// ...
},
});