diff --git a/docs/src/docs/core/MultiSelect.mdx b/docs/src/docs/core/MultiSelect.mdx
index b9828d7dcbf..4a2297e9eed 100644
--- a/docs/src/docs/core/MultiSelect.mdx
+++ b/docs/src/docs/core/MultiSelect.mdx
@@ -106,6 +106,12 @@ Set `searchable` prop to enable search in select and `nothingFound` prop to prov
+## Controlled search
+
+Set `searchValue` and `onSearchChange` prop to enable controlled search in select:
+
+
+
## Clearable
Set `clearable` prop to enable clearing all values at once.
diff --git a/docs/src/docs/core/Select.mdx b/docs/src/docs/core/Select.mdx
index 1b72a9a3525..552553d5b82 100644
--- a/docs/src/docs/core/Select.mdx
+++ b/docs/src/docs/core/Select.mdx
@@ -106,6 +106,12 @@ Set `searchable` prop to enable search in select and `nothingFound` prop to prov
+## Controlled search
+
+Set `searchValue` and `onSearchChange` prop to enable controlled search in select:
+
+
+
## Clearable
Set `clearable` prop to enable clearing select value.
diff --git a/src/mantine-core/src/MultiSelect/MultiSelect.tsx b/src/mantine-core/src/MultiSelect/MultiSelect.tsx
index 00976578452..82e36307d35 100644
--- a/src/mantine-core/src/MultiSelect/MultiSelect.tsx
+++ b/src/mantine-core/src/MultiSelect/MultiSelect.tsx
@@ -65,6 +65,9 @@ export interface MultiSelectProps
/** Called each time search query changes */
onSearchChange?(query: string): void;
+ /** Controlled search input value */
+ searchValue?: string;
+
/** Allow creatable option */
creatable?: boolean;
@@ -177,6 +180,7 @@ export const MultiSelect = forwardRef((props
clearButtonLabel,
variant,
onSearchChange,
+ searchValue,
disabled,
initiallyOpened,
radius,
@@ -225,7 +229,12 @@ export const MultiSelect = forwardRef((props
const [dropdownOpened, _setDropdownOpened] = useState(initiallyOpened);
const [hovered, setHovered] = useState(-1);
const [direction, setDirection] = useState('column');
- const [searchValue, setSearchValue] = useState('');
+ const [_searchValue, handleSearchChange] = useUncontrolled({
+ value: searchValue,
+ defaultValue: '',
+ finalValue: undefined,
+ onChange: onSearchChange,
+ });
const [IMEOpen, setIMEOpen] = useState(false);
const { scrollIntoView, targetRef, scrollableRef } = useScrollIntoView({
@@ -244,11 +253,6 @@ export const MultiSelect = forwardRef((props
typeof handler === 'function' && handler();
};
- const handleSearchChange = (val: string) => {
- typeof onSearchChange === 'function' && onSearchChange(val);
- setSearchValue(val);
- };
-
const formattedData = data.map((item) =>
typeof item === 'string' ? { label: item, value: item } : item
);
@@ -288,7 +292,7 @@ export const MultiSelect = forwardRef((props
const filteredData = filterData({
data: sortedData,
searchable,
- searchValue,
+ searchValue: _searchValue,
limit,
filter,
value: _value,
@@ -309,7 +313,7 @@ export const MultiSelect = forwardRef((props
useDidUpdate(() => {
setHovered(-1);
- }, [searchValue]);
+ }, [_searchValue]);
useDidUpdate(() => {
if (!disabled && _value.length > data.length) {
@@ -472,7 +476,7 @@ export const MultiSelect = forwardRef((props
}
case 'Backspace': {
- if (_value.length > 0 && searchValue.length === 0) {
+ if (_value.length > 0 && _searchValue.length === 0) {
setValue(_value.slice(0, -1));
setDropdownOpened(true);
if (maxSelectedValues) {
@@ -563,9 +567,9 @@ export const MultiSelect = forwardRef((props
}
};
- if (isCreatable && shouldCreate(searchValue, sortedData)) {
- createLabel = getCreateLabel(searchValue);
- filteredData.push({ label: searchValue, value: searchValue, creatable: true });
+ if (isCreatable && shouldCreate(_searchValue, sortedData)) {
+ createLabel = getCreateLabel(_searchValue);
+ filteredData.push({ label: _searchValue, value: _searchValue, creatable: true });
}
const shouldRenderDropdown =
@@ -682,7 +686,7 @@ export const MultiSelect = forwardRef((props
[classes.searchInputEmpty]: _value.length === 0,
})}
onKeyDown={handleInputKeydown}
- value={searchValue}
+ value={_searchValue}
onChange={handleInputChange}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
diff --git a/src/mantine-core/src/Select/Select.tsx b/src/mantine-core/src/Select/Select.tsx
index 3919b905b04..11ff498e8f5 100644
--- a/src/mantine-core/src/Select/Select.tsx
+++ b/src/mantine-core/src/Select/Select.tsx
@@ -97,6 +97,9 @@ export interface SelectProps
/** Called each time search value changes */
onSearchChange?(query: string): void;
+ /** Controlled search input value */
+ searchValue?: string;
+
/** Allow creatable option */
creatable?: boolean;
@@ -187,6 +190,7 @@ export const Select = forwardRef((props, ref) =>
limit,
disabled,
onSearchChange,
+ searchValue,
rightSection,
rightSectionWidth,
creatable,
@@ -253,7 +257,13 @@ export const Select = forwardRef((props, ref) =>
});
const selectedValue = sortedData.find((item) => item.value === _value);
- const [inputValue, setInputValue] = useState(selectedValue?.label || '');
+
+ const [inputValue, setInputValue] = useUncontrolled({
+ value: searchValue,
+ defaultValue: selectedValue?.label || '',
+ finalValue: undefined,
+ onChange: onSearchChange,
+ });
const handleSearchChange = (val: string) => {
setInputValue(val);
diff --git a/src/mantine-demos/src/demos/core/MultiSelect/MultiSelect.demo.searchableControlled.tsx b/src/mantine-demos/src/demos/core/MultiSelect/MultiSelect.demo.searchableControlled.tsx
new file mode 100644
index 00000000000..8f367b64338
--- /dev/null
+++ b/src/mantine-demos/src/demos/core/MultiSelect/MultiSelect.demo.searchableControlled.tsx
@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import { MultiSelect } from '@mantine/core';
+import { data } from './_data';
+
+const code = `
+import { MultiSelect } from '@mantine/core';
+
+function Demo() {
+ const [searchValue, onSearchChange] = useState('');
+
+ return (
+
+ );
+}
+`;
+
+function Demo() {
+ const [searchValue, onSearchChange] = useState('');
+
+ return (
+
+
+
+ );
+}
+
+export const searchableControlled: MantineDemo = {
+ type: 'demo',
+ code,
+ component: Demo,
+};
diff --git a/src/mantine-demos/src/demos/core/MultiSelect/index.ts b/src/mantine-demos/src/demos/core/MultiSelect/index.ts
index 838beda8a1c..4dc7b80e08c 100644
--- a/src/mantine-demos/src/demos/core/MultiSelect/index.ts
+++ b/src/mantine-demos/src/demos/core/MultiSelect/index.ts
@@ -1,6 +1,7 @@
export { countries } from './MultiSelect.demo.countries';
export { usage } from './MultiSelect.demo.usage';
export { searchable } from './MultiSelect.demo.searchable';
+export { searchableControlled } from './MultiSelect.demo.searchableControlled';
export { clearable } from './MultiSelect.demo.clearable';
export { configurator } from './MultiSelect.demo.configurator';
export { flip } from './MultiSelect.demo.flip';
diff --git a/src/mantine-demos/src/demos/core/Select/Select.demo.searchableControlled.tsx b/src/mantine-demos/src/demos/core/Select/Select.demo.searchableControlled.tsx
new file mode 100644
index 00000000000..545c61d078b
--- /dev/null
+++ b/src/mantine-demos/src/demos/core/Select/Select.demo.searchableControlled.tsx
@@ -0,0 +1,46 @@
+import React, { useState } from 'react';
+import { Select } from '@mantine/core';
+
+const code = `
+import { Select } from '@mantine/core';
+
+function Demo() {
+ const [searchValue, onSearchChange] = useState('');
+
+ return (
+
+ );
+}
+`;
+
+function Demo() {
+ const [searchValue, onSearchChange] = useState('');
+
+ return (
+
+
+
+ );
+}
+
+export const searchableControlled: MantineDemo = {
+ type: 'demo',
+ code,
+ component: Demo,
+};
diff --git a/src/mantine-demos/src/demos/core/Select/index.ts b/src/mantine-demos/src/demos/core/Select/index.ts
index 1bdc8c7eed5..3f2af24aba6 100644
--- a/src/mantine-demos/src/demos/core/Select/index.ts
+++ b/src/mantine-demos/src/demos/core/Select/index.ts
@@ -1,5 +1,6 @@
export { usage } from './Select.demo.usage';
export { searchable } from './Select.demo.searchable';
+export { searchableControlled } from './Select.demo.searchableControlled';
export { large } from './Select.demo.large';
export { configurator } from './Select.demo.configurator';
export { flip } from './Select.demo.flip';