From c55e128d387d7ba287e59d42c539abf9f0b26df1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Sat, 28 Dec 2024 19:02:34 +0530 Subject: [PATCH 01/12] fix shrink animation in Autocomplete when value is set on initial render --- packages/mui-material/src/useAutocomplete/useAutocomplete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 35789e85e2917c..c49e535c7e95b2 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -1079,7 +1079,7 @@ function useAutocomplete(props) { }), getInputProps: () => ({ id, - value: inputValue, + value: inputValue ?? getOptionLabel(value), onBlur: handleBlur, onFocus: handleFocus, onChange: handleInputChange, From 1154102f0f5617f6a8fb9d9d03ebdd779e697be0 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 1 Jan 2025 12:54:24 +0530 Subject: [PATCH 02/12] set default input value on first render --- .../mui-material/src/useAutocomplete/useAutocomplete.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index c49e535c7e95b2..0a0451bc21270e 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -1065,6 +1065,13 @@ function useAutocomplete(props) { handleBlur(); } + let defaultInputValue = ''; + if (typeof valueProp === 'string') { + defaultInputValue = valueProp; + } else if (!multiple && value != null && typeof valueProp === 'object') { + defaultInputValue = getOptionLabel(valueProp); + } + return { getRootProps: (other = {}) => ({ 'aria-owns': listboxAvailable ? `${id}-listbox` : null, @@ -1079,7 +1086,7 @@ function useAutocomplete(props) { }), getInputProps: () => ({ id, - value: inputValue ?? getOptionLabel(value), + value: inputValue || defaultInputValue, onBlur: handleBlur, onFocus: handleFocus, onChange: handleInputChange, From 7743444b3333b5163e2de74291e886f86eaf4357 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 1 Jan 2025 18:03:48 +0530 Subject: [PATCH 03/12] derive input value on first render only --- .../src/useAutocomplete/useAutocomplete.js | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 0a0451bc21270e..c58dd61179be6a 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -61,6 +61,22 @@ const defaultIsActiveElementInListbox = (listboxRef) => const MULTIPLE_DEFAULT_VALUE = []; +function getInitialInputValue(value, defaultValue, multiple, getOptionLabel) { + if (typeof value === 'string') { + return value; + } + if (!multiple && value != null && typeof value === 'object') { + return getOptionLabel(value); + } + if (typeof defaultValue === 'string') { + return defaultValue; + } + if (!multiple && defaultValue != null && typeof defaultValue === 'object') { + return getOptionLabel(defaultValue); + } + return ''; +} + function useAutocomplete(props) { const { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -137,6 +153,12 @@ function useAutocomplete(props) { const defaultHighlighted = autoHighlight ? 0 : -1; const highlightedIndexRef = React.useRef(defaultHighlighted); + const initialInputValue = React.useMemo( + () => getInitialInputValue(valueProp, defaultValue, multiple, getOptionLabel), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + const [value, setValueState] = useControlled({ controlled: valueProp, default: defaultValue, @@ -144,7 +166,7 @@ function useAutocomplete(props) { }); const [inputValue, setInputValueState] = useControlled({ controlled: inputValueProp, - default: '', + default: initialInputValue, name: componentName, state: 'inputValue', }); @@ -1065,13 +1087,6 @@ function useAutocomplete(props) { handleBlur(); } - let defaultInputValue = ''; - if (typeof valueProp === 'string') { - defaultInputValue = valueProp; - } else if (!multiple && value != null && typeof valueProp === 'object') { - defaultInputValue = getOptionLabel(valueProp); - } - return { getRootProps: (other = {}) => ({ 'aria-owns': listboxAvailable ? `${id}-listbox` : null, @@ -1086,7 +1101,7 @@ function useAutocomplete(props) { }), getInputProps: () => ({ id, - value: inputValue || defaultInputValue, + value: inputValue, onBlur: handleBlur, onFocus: handleFocus, onChange: handleInputChange, From 00da0ea59f9caf8174a582064fdf8428f5b79d38 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 2 Jan 2025 11:33:45 +0530 Subject: [PATCH 04/12] improve --- .../src/useAutocomplete/useAutocomplete.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index c58dd61179be6a..826adc4a7b4621 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -62,19 +62,19 @@ const defaultIsActiveElementInListbox = (listboxRef) => const MULTIPLE_DEFAULT_VALUE = []; function getInitialInputValue(value, defaultValue, multiple, getOptionLabel) { + if (multiple || (value == null && defaultValue == null)) { + return ''; + } if (typeof value === 'string') { return value; } - if (!multiple && value != null && typeof value === 'object') { + if (typeof value === 'object') { return getOptionLabel(value); } - if (typeof defaultValue === 'string') { - return defaultValue; - } - if (!multiple && defaultValue != null && typeof defaultValue === 'object') { + if (typeof defaultValue === 'object') { return getOptionLabel(defaultValue); } - return ''; + return defaultValue || ''; } function useAutocomplete(props) { From 1a7a2db691c96dbd7beaadbc5b70be5f5e929030 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 2 Jan 2025 11:51:56 +0530 Subject: [PATCH 05/12] fix logic --- packages/mui-material/src/useAutocomplete/useAutocomplete.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 826adc4a7b4621..46593a711adfcc 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -68,10 +68,10 @@ function getInitialInputValue(value, defaultValue, multiple, getOptionLabel) { if (typeof value === 'string') { return value; } - if (typeof value === 'object') { + if (value !== null && typeof value === 'object') { return getOptionLabel(value); } - if (typeof defaultValue === 'object') { + if (defaultValue !== null && typeof defaultValue === 'object') { return getOptionLabel(defaultValue); } return defaultValue || ''; From d61b5418693e729c20e0f985141f9882f4e32e0f Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 2 Jan 2025 12:55:09 +0530 Subject: [PATCH 06/12] Add comment --- packages/mui-material/src/useAutocomplete/useAutocomplete.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 46593a711adfcc..19a4e84efb335f 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -153,6 +153,9 @@ function useAutocomplete(props) { const defaultHighlighted = autoHighlight ? 0 : -1; const highlightedIndexRef = React.useRef(defaultHighlighted); + // Caculate the initial inputValue only on first render (mount) + // Ensurses that the initialInputValue remains constant during the component's lifetime + // defaultValue and value do not need to dynamically affect the inputValue after initialization const initialInputValue = React.useMemo( () => getInitialInputValue(valueProp, defaultValue, multiple, getOptionLabel), // eslint-disable-next-line react-hooks/exhaustive-deps From d4332f079f707ba42ba2310866489929ca079ed7 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Sat, 4 Jan 2025 12:11:06 +0530 Subject: [PATCH 07/12] derive initial input value only from default value --- .../src/useAutocomplete/useAutocomplete.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 19a4e84efb335f..f90dba35bd76b3 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -61,20 +61,11 @@ const defaultIsActiveElementInListbox = (listboxRef) => const MULTIPLE_DEFAULT_VALUE = []; -function getInitialInputValue(value, defaultValue, multiple, getOptionLabel) { - if (multiple || (value == null && defaultValue == null)) { +function getInitialInputValue(defaultValue, multiple, getOptionLabel) { + if (multiple || defaultValue == null) { return ''; } - if (typeof value === 'string') { - return value; - } - if (value !== null && typeof value === 'object') { - return getOptionLabel(value); - } - if (defaultValue !== null && typeof defaultValue === 'object') { - return getOptionLabel(defaultValue); - } - return defaultValue || ''; + return typeof defaultValue === 'object' ? getOptionLabel(defaultValue) : defaultValue; } function useAutocomplete(props) { @@ -155,9 +146,9 @@ function useAutocomplete(props) { // Caculate the initial inputValue only on first render (mount) // Ensurses that the initialInputValue remains constant during the component's lifetime - // defaultValue and value do not need to dynamically affect the inputValue after initialization + // defaultValue does not need to dynamically affect the inputValue after initialization const initialInputValue = React.useMemo( - () => getInitialInputValue(valueProp, defaultValue, multiple, getOptionLabel), + () => getInitialInputValue(defaultValue, multiple, getOptionLabel), // eslint-disable-next-line react-hooks/exhaustive-deps [], ); From fe990dde7da9d06853f044d44182dead2f7280c1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Sat, 4 Jan 2025 12:35:26 +0530 Subject: [PATCH 08/12] use useState initializer function instead of useMemo --- .../mui-material/src/useAutocomplete/useAutocomplete.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index f90dba35bd76b3..11fce4d27c342f 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -145,12 +145,10 @@ function useAutocomplete(props) { const highlightedIndexRef = React.useRef(defaultHighlighted); // Caculate the initial inputValue only on first render (mount) - // Ensurses that the initialInputValue remains constant during the component's lifetime + // Ensures that the initialInputValue remains constant during the component's lifetime // defaultValue does not need to dynamically affect the inputValue after initialization - const initialInputValue = React.useMemo( - () => getInitialInputValue(defaultValue, multiple, getOptionLabel), - // eslint-disable-next-line react-hooks/exhaustive-deps - [], + const [initialInputValue] = React.useState(() => + getInitialInputValue(defaultValue, multiple, getOptionLabel), ); const [value, setValueState] = useControlled({ From d45ad3b2a561da692c1fbe679d8b6510205092e8 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 9 Jan 2025 13:55:31 +0530 Subject: [PATCH 09/12] dont use initializer function in useState --- .../mui-material/src/useAutocomplete/useAutocomplete.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 11fce4d27c342f..7228d544b9454a 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -144,13 +144,6 @@ function useAutocomplete(props) { const defaultHighlighted = autoHighlight ? 0 : -1; const highlightedIndexRef = React.useRef(defaultHighlighted); - // Caculate the initial inputValue only on first render (mount) - // Ensures that the initialInputValue remains constant during the component's lifetime - // defaultValue does not need to dynamically affect the inputValue after initialization - const [initialInputValue] = React.useState(() => - getInitialInputValue(defaultValue, multiple, getOptionLabel), - ); - const [value, setValueState] = useControlled({ controlled: valueProp, default: defaultValue, @@ -158,7 +151,7 @@ function useAutocomplete(props) { }); const [inputValue, setInputValueState] = useControlled({ controlled: inputValueProp, - default: initialInputValue, + default: getInitialInputValue(defaultValue, multiple, getOptionLabel), name: componentName, state: 'inputValue', }); From 8531773e632fc5439e726e1ceca354cabd1e10f7 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 9 Jan 2025 17:59:39 +0530 Subject: [PATCH 10/12] use ref for initializing default input value --- .../mui-material/src/useAutocomplete/useAutocomplete.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index 7228d544b9454a..e6d2bd993e6106 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -144,6 +144,10 @@ function useAutocomplete(props) { const defaultHighlighted = autoHighlight ? 0 : -1; const highlightedIndexRef = React.useRef(defaultHighlighted); + const initialInputValue = React.useRef( + getInitialInputValue(defaultValue, multiple, getOptionLabel), + ).current; + const [value, setValueState] = useControlled({ controlled: valueProp, default: defaultValue, @@ -151,7 +155,7 @@ function useAutocomplete(props) { }); const [inputValue, setInputValueState] = useControlled({ controlled: inputValueProp, - default: getInitialInputValue(defaultValue, multiple, getOptionLabel), + default: initialInputValue, name: componentName, state: 'inputValue', }); From d7077b6c6288cdd21ed957a73f75ac7c1b4c656b Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 10 Jan 2025 18:06:18 +0530 Subject: [PATCH 11/12] refactor to use one method --- .../src/useAutocomplete/useAutocomplete.js | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js index e6d2bd993e6106..6f5b5ca0af62cc 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js @@ -61,11 +61,12 @@ const defaultIsActiveElementInListbox = (listboxRef) => const MULTIPLE_DEFAULT_VALUE = []; -function getInitialInputValue(defaultValue, multiple, getOptionLabel) { - if (multiple || defaultValue == null) { +function getInputValue(value, multiple, getOptionLabel) { + if (multiple || value == null) { return ''; } - return typeof defaultValue === 'object' ? getOptionLabel(defaultValue) : defaultValue; + const optionLabel = getOptionLabel(value); + return typeof optionLabel === 'string' ? optionLabel : ''; } function useAutocomplete(props) { @@ -144,8 +145,10 @@ function useAutocomplete(props) { const defaultHighlighted = autoHighlight ? 0 : -1; const highlightedIndexRef = React.useRef(defaultHighlighted); + // Calculate the initial inputValue on mount only. + // Using useRef since defaultValue doesn't need to update inputValue dynamically. const initialInputValue = React.useRef( - getInitialInputValue(defaultValue, multiple, getOptionLabel), + getInputValue(defaultValue, multiple, getOptionLabel), ).current; const [value, setValueState] = useControlled({ @@ -170,15 +173,7 @@ function useAutocomplete(props) { if (!isOptionSelected && !clearOnBlur) { return; } - let newInputValue; - if (multiple) { - newInputValue = ''; - } else if (newValue == null) { - newInputValue = ''; - } else { - const optionLabel = getOptionLabel(newValue); - newInputValue = typeof optionLabel === 'string' ? optionLabel : ''; - } + const newInputValue = getInputValue(newValue, multiple, getOptionLabel); if (inputValue === newInputValue) { return; From 4c513354b83a71e4383554342ae3f79b6e739508 Mon Sep 17 00:00:00 2001 From: DiegoAndai Date: Fri, 17 Jan 2025 13:10:20 -0300 Subject: [PATCH 12/12] Add test spying on onInputChange --- .../useAutocomplete/useAutocomplete.test.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.test.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.test.js index 01dd8dd4b7880a..0c39bfe0724317 100644 --- a/packages/mui-material/src/useAutocomplete/useAutocomplete.test.js +++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.test.js @@ -395,4 +395,24 @@ describe('useAutocomplete', () => { fireEvent.click(button); }).not.to.throw(); }); + + describe('prop: defaultValue', () => { + it('should not trigger onInputChange when defaultValue is provided', () => { + const onInputChange = spy(); + const defaultValue = 'foo'; + + function Test() { + const { getInputProps } = useAutocomplete({ + defaultValue, + onInputChange, + options: ['foo', 'bar'], + }); + + return ; + } + + render(); + expect(onInputChange.callCount).to.equal(0); + }); + }); });