The QueryList component is a higher-order component that manages query string interactions with item lists. It serves as the foundation for all other select components in the library.
Higher-order React component for managing query interactions with item lists.
/**
* QueryList higher-order component for managing query interactions
* @template T - Type of items in the list
*/
class QueryList<T> extends React.Component<QueryListProps<T>, QueryListState<T>> {
/** Generic factory method for type inference */
static ofType<U>(): new (props: QueryListProps<U>) => QueryList<U>;
/** Scroll the active item into view */
scrollActiveItemIntoView(): void;
/** Set the query string and optionally reset active item */
setQuery(query: string, resetActiveItem?: boolean, props?: QueryListProps<T>): void;
/** Set the currently active item */
setActiveItem(activeItem: T | CreateNewItem | null): void;
}
interface QueryListProps<T> extends ListItemsProps<T> {
/** Function to render the query list UI */
renderer: (listProps: QueryListRendererProps<T>) => React.JSX.Element;
/** Initial item to be active when component mounts */
initialActiveItem?: T;
/** Props for the menu container element */
menuProps?: React.HTMLAttributes<HTMLUListElement>;
/** Keyboard event handler for keydown events */
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
/** Keyboard event handler for keyup events */
onKeyUp?: React.KeyboardEventHandler<HTMLElement>;
/** Whether the component is disabled */
disabled?: boolean;
}
interface QueryListState<T> {
/** Currently active item */
activeItem: T | CreateNewItem | null;
/** Items that can be created from current query */
createNewItem: T | T[] | undefined;
/** Items after applying filtering predicates */
filteredItems: T[];
/** Current query string */
query: string;
}
interface QueryListRendererProps<T> extends Pick<QueryListState<T>, "activeItem" | "filteredItems" | "query"> {
/** Function to handle item selection */
handleItemSelect: (item: T, event?: React.SyntheticEvent<HTMLElement>) => void;
/** Function to handle paste operations */
handlePaste: (queries: string[]) => void;
/** Keyboard handler for key down events */
handleKeyDown: React.KeyboardEventHandler<HTMLElement>;
/** Keyboard handler for key up events */
handleKeyUp: React.KeyboardEventHandler<HTMLElement>;
/** Handler for query string changes */
handleQueryChange: React.ChangeEventHandler<HTMLInputElement>;
/** Rendered list of items */
itemList: React.ReactNode;
}Usage Examples:
import React, { useState } from "react";
import { QueryList, ItemRenderer, QueryListRendererProps } from "@blueprintjs/select";
import { InputGroup, Menu } from "@blueprintjs/core";
interface Task {
id: number;
title: string;
completed: boolean;
priority: "high" | "medium" | "low";
}
const tasks: Task[] = [
{ id: 1, title: "Review pull request", completed: false, priority: "high" },
{ id: 2, title: "Update documentation", completed: false, priority: "medium" },
{ id: 3, title: "Fix bug in login flow", completed: false, priority: "high" },
{ id: 4, title: "Prepare demo presentation", completed: true, priority: "low" },
];
const renderTask: ItemRenderer<Task> = (task, { handleClick, modifiers }) => {
const priorityColor = {
high: "#DB3737",
medium: "#F29D49",
low: "#0F9960",
}[task.priority];
return (
<div
onClick={handleClick}
style={{
padding: "8px 12px",
background: modifiers.active ? "#137CBD" : "transparent",
color: modifiers.active ? "white" : "black",
cursor: "pointer",
opacity: task.completed ? 0.6 : 1,
}}
>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ textDecoration: task.completed ? "line-through" : "none" }}>
{task.title}
</span>
<div
style={{
width: "8px",
height: "8px",
borderRadius: "50%",
backgroundColor: priorityColor,
}}
/>
</div>
</div>
);
};
const TaskQueryList = () => {
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
const renderer = (listProps: QueryListRendererProps<Task>) => {
const { query, handleQueryChange, handleKeyDown, handleKeyUp, itemList } = listProps;
return (
<div style={{ width: "300px", border: "1px solid #ccc", borderRadius: "4px" }}>
<InputGroup
placeholder="Search tasks..."
value={query}
onChange={handleQueryChange}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
leftIcon="search"
/>
<Menu style={{ maxHeight: "200px", overflow: "auto" }}>
{itemList}
</Menu>
</div>
);
};
const taskPredicate = (query: string, task: Task) => {
return task.title.toLowerCase().includes(query.toLowerCase());
};
return (
<div>
<QueryList<Task>
items={tasks}
itemRenderer={renderTask}
itemPredicate={taskPredicate}
onItemSelect={setSelectedTask}
renderer={renderer}
noResults={<div style={{ padding: "8px", textAlign: "center" }}>No tasks found</div>}
/>
{selectedTask && (
<div style={{ marginTop: "16px", padding: "12px", background: "#f5f5f5" }}>
<strong>Selected:</strong> {selectedTask.title}
<br />
<strong>Priority:</strong> {selectedTask.priority}
<br />
<strong>Status:</strong> {selectedTask.completed ? "Completed" : "Pending"}
</div>
)}
</div>
);
};QueryList provides complete control over the UI through the renderer prop.
Usage Examples:
// Grid layout renderer
const GridRenderer = (listProps: QueryListRendererProps<Task>) => {
const { query, handleQueryChange, handleKeyDown, itemList } = listProps;
return (
<div style={{ width: "600px" }}>
<InputGroup
placeholder="Search tasks..."
value={query}
onChange={handleQueryChange}
onKeyDown={handleKeyDown}
large
leftIcon="search"
/>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
gap: "12px",
marginTop: "12px",
maxHeight: "400px",
overflow: "auto",
}}
>
{itemList}
</div>
</div>
);
};
// Table layout renderer
const TableRenderer = (listProps: QueryListRendererProps<Task>) => {
const { query, handleQueryChange, itemList } = listProps;
return (
<div style={{ width: "100%" }}>
<InputGroup
placeholder="Search tasks..."
value={query}
onChange={handleQueryChange}
style={{ marginBottom: "12px" }}
/>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
<tr style={{ borderBottom: "2px solid #ccc" }}>
<th style={{ padding: "8px", textAlign: "left" }}>Task</th>
<th style={{ padding: "8px", textAlign: "left" }}>Priority</th>
<th style={{ padding: "8px", textAlign: "left" }}>Status</th>
</tr>
</thead>
<tbody>
{itemList}
</tbody>
</table>
</div>
);
};
// Corresponding table item renderer
const renderTaskRow: ItemRenderer<Task> = (task, { handleClick, modifiers }) => (
<tr
onClick={handleClick}
style={{
backgroundColor: modifiers.active ? "#137CBD" : "transparent",
color: modifiers.active ? "white" : "black",
cursor: "pointer",
}}
>
<td style={{ padding: "8px" }}>{task.title}</td>
<td style={{ padding: "8px" }}>{task.priority}</td>
<td style={{ padding: "8px" }}>{task.completed ? "✓" : "○"}</td>
</tr>
);QueryList provides built-in keyboard navigation that can be extended with custom handlers.
Usage Examples:
const KeyboardQueryList = () => {
const queryListRef = React.useRef<QueryList<Task>>(null);
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
// Custom keyboard shortcuts
switch (event.key) {
case "ArrowUp":
case "ArrowDown":
// Let QueryList handle arrow navigation
break;
case "Enter":
// Let QueryList handle selection
break;
case "Escape":
// Custom: clear query
if (queryListRef.current) {
queryListRef.current.setQuery("", true);
}
event.preventDefault();
break;
case "Home":
// Custom: jump to first item
if (queryListRef.current && event.ctrlKey) {
const firstItem = tasks[0];
if (firstItem) {
queryListRef.current.setActiveItem(firstItem);
}
event.preventDefault();
}
break;
case "End":
// Custom: jump to last item
if (queryListRef.current && event.ctrlKey) {
const lastItem = tasks[tasks.length - 1];
if (lastItem) {
queryListRef.current.setActiveItem(lastItem);
}
event.preventDefault();
}
break;
}
};
const renderer = (listProps: QueryListRendererProps<Task>) => (
<div>
<InputGroup
placeholder="Type to search, arrows to navigate, Enter to select, Esc to clear..."
value={listProps.query}
onChange={listProps.handleQueryChange}
onKeyDown={listProps.handleKeyDown}
/>
<div>{listProps.itemList}</div>
</div>
);
return (
<QueryList<Task>
ref={queryListRef}
items={tasks}
itemRenderer={renderTask}
onItemSelect={(task) => console.log("Selected:", task.title)}
renderer={renderer}
onKeyDown={handleKeyDown}
/>
);
};QueryList provides methods for programmatically managing the active item.
Usage Examples:
const ActiveItemQueryList = () => {
const queryListRef = React.useRef<QueryList<Task>>(null);
const [externalActiveItem, setExternalActiveItem] = useState<Task | null>(null);
// External controls
const setRandomActive = () => {
if (queryListRef.current && tasks.length > 0) {
const randomTask = tasks[Math.floor(Math.random() * tasks.length)];
queryListRef.current.setActiveItem(randomTask);
setExternalActiveItem(randomTask);
}
};
const clearActive = () => {
if (queryListRef.current) {
queryListRef.current.setActiveItem(null);
setExternalActiveItem(null);
}
};
const scrollToActive = () => {
if (queryListRef.current) {
queryListRef.current.scrollActiveItemIntoView();
}
};
const renderer = (listProps: QueryListRendererProps<Task>) => (
<div>
<div style={{ marginBottom: "12px", display: "flex", gap: "8px" }}>
<button onClick={setRandomActive}>Random Active</button>
<button onClick={clearActive}>Clear Active</button>
<button onClick={scrollToActive}>Scroll to Active</button>
</div>
<InputGroup
placeholder="Search tasks..."
value={listProps.query}
onChange={listProps.handleQueryChange}
onKeyDown={listProps.handleKeyDown}
/>
<div style={{ maxHeight: "200px", overflow: "auto" }}>
{listProps.itemList}
</div>
{externalActiveItem && (
<div style={{ marginTop: "8px", fontSize: "12px", color: "#666" }}>
Active: {externalActiveItem.title}
</div>
)}
</div>
);
return (
<QueryList<Task>
ref={queryListRef}
items={tasks}
itemRenderer={renderTask}
onItemSelect={(task) => console.log("Selected:", task.title)}
onActiveItemChange={(activeItem) => {
setExternalActiveItem(activeItem as Task | null);
}}
renderer={renderer}
scrollToActiveItem
/>
);
};QueryList includes utility functions for managing enabled items.
/**
* Find the first enabled item in a list
* @param items - Array of items to search
* @param itemDisabled - Function or property key to determine if item is disabled
* @param direction - Search direction (1 for forward, -1 for backward)
* @param startIndex - Index to start search from
* @returns First enabled item or null if none found
*/
function getFirstEnabledItem<T>(
items: T[],
itemDisabled?: keyof T | ((item: T, index: number) => boolean),
direction?: number,
startIndex?: number,
): T | CreateNewItem | null;Usage Examples:
import { getFirstEnabledItem } from "@blueprintjs/select";
const itemsWithDisabled = [
{ name: "Item 1", disabled: true },
{ name: "Item 2", disabled: false },
{ name: "Item 3", disabled: false },
{ name: "Item 4", disabled: true },
];
// Find first enabled item using property key
const firstEnabled = getFirstEnabledItem(itemsWithDisabled, "disabled");
console.log(firstEnabled); // { name: "Item 2", disabled: false }
// Find first enabled item using function
const firstEnabledFn = getFirstEnabledItem(
itemsWithDisabled,
(item) => item.disabled,
);
// Find first enabled item starting from index 2, going backward
const firstEnabledBackward = getFirstEnabledItem(
itemsWithDisabled,
"disabled",
-1, // backward direction
2, // start from index 2
);