|
1 |
| -import React, { useState, useRef, useEffect, useMemo } from 'react'; |
| 1 | +import React, { |
| 2 | + useState, |
| 3 | + useRef, |
| 4 | + useEffect, |
| 5 | + useMemo, |
| 6 | + useCallback |
| 7 | +} from 'react'; |
2 | 8 | import {
|
3 | 9 | TextField,
|
4 | 10 | MenuItem,
|
@@ -177,76 +183,85 @@ export function SimpleAutocomplete(
|
177 | 183 | // Determine if menu should be open
|
178 | 184 | const shouldShowMenu = isOpen && filteredOptions.length > 0;
|
179 | 185 |
|
180 |
| - function handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void { |
181 |
| - const newValue = event.target.value; |
182 |
| - setInputValue(newValue); |
183 |
| - setFocusedIndex(-1); |
184 |
| - |
185 |
| - if (!isOpen && newValue.trim() !== '') { |
186 |
| - setIsOpen(true); |
187 |
| - } |
188 |
| - |
189 |
| - if (props.onChange) { |
190 |
| - props.onChange(newValue); |
191 |
| - } |
192 |
| - } |
| 186 | + const handleInputChange = useCallback( |
| 187 | + (event: React.ChangeEvent<HTMLInputElement>): void => { |
| 188 | + const newValue = event.target.value; |
| 189 | + setInputValue(newValue); |
| 190 | + setFocusedIndex(-1); |
| 191 | + |
| 192 | + if (!isOpen && newValue.trim() !== '') { |
| 193 | + setIsOpen(true); |
| 194 | + } |
| 195 | + |
| 196 | + if (props.onChange) { |
| 197 | + props.onChange(newValue); |
| 198 | + } |
| 199 | + }, |
| 200 | + [isOpen, props.onChange] |
| 201 | + ); |
193 | 202 |
|
194 |
| - function handleInputFocus(): void { |
| 203 | + const handleInputFocus = useCallback((): void => { |
195 | 204 | setIsOpen(true);
|
196 |
| - } |
197 |
| - |
198 |
| - function handleOptionClick(option: AutocompleteOption): void { |
199 |
| - setInputValue(option.value); |
200 |
| - setIsOpen(false); |
201 |
| - setFocusedIndex(-1); |
202 |
| - |
203 |
| - if (props.onChange) { |
204 |
| - props.onChange(option.value); |
205 |
| - } |
206 |
| - |
207 |
| - if (inputRef.current) { |
208 |
| - inputRef.current.blur(); |
209 |
| - } |
210 |
| - } |
211 |
| - |
212 |
| - function handleKeyDown(event: React.KeyboardEvent): void { |
213 |
| - if (!shouldShowMenu) { |
214 |
| - return; |
215 |
| - } |
| 205 | + }, []); |
| 206 | + |
| 207 | + const handleOptionClick = useCallback( |
| 208 | + (option: AutocompleteOption): void => { |
| 209 | + setInputValue(option.value); |
| 210 | + setIsOpen(false); |
| 211 | + setFocusedIndex(-1); |
| 212 | + |
| 213 | + if (props.onChange) { |
| 214 | + props.onChange(option.value); |
| 215 | + } |
| 216 | + |
| 217 | + if (inputRef.current) { |
| 218 | + inputRef.current.blur(); |
| 219 | + } |
| 220 | + }, |
| 221 | + [props.onChange] |
| 222 | + ); |
216 | 223 |
|
217 |
| - switch (event.key) { |
218 |
| - case 'ArrowDown': |
219 |
| - event.preventDefault(); |
220 |
| - setFocusedIndex(prev => { |
221 |
| - return prev < filteredOptions.length - 1 ? prev + 1 : 0; |
222 |
| - }); |
223 |
| - break; |
224 |
| - |
225 |
| - case 'ArrowUp': |
226 |
| - event.preventDefault(); |
227 |
| - setFocusedIndex(prev => { |
228 |
| - return prev > 0 ? prev - 1 : filteredOptions.length - 1; |
229 |
| - }); |
230 |
| - break; |
231 |
| - |
232 |
| - case 'Enter': |
233 |
| - event.preventDefault(); |
234 |
| - if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) { |
235 |
| - handleOptionClick(filteredOptions[focusedIndex]); |
236 |
| - } |
237 |
| - break; |
238 |
| - |
239 |
| - case 'Escape': |
240 |
| - setIsOpen(false); |
241 |
| - setFocusedIndex(-1); |
242 |
| - break; |
243 |
| - } |
244 |
| - } |
| 224 | + const handleKeyDown = useCallback( |
| 225 | + (event: React.KeyboardEvent): void => { |
| 226 | + if (!shouldShowMenu) { |
| 227 | + return; |
| 228 | + } |
| 229 | + |
| 230 | + switch (event.key) { |
| 231 | + case 'ArrowDown': |
| 232 | + event.preventDefault(); |
| 233 | + setFocusedIndex(prev => { |
| 234 | + return prev < filteredOptions.length - 1 ? prev + 1 : 0; |
| 235 | + }); |
| 236 | + break; |
| 237 | + |
| 238 | + case 'ArrowUp': |
| 239 | + event.preventDefault(); |
| 240 | + setFocusedIndex(prev => { |
| 241 | + return prev > 0 ? prev - 1 : filteredOptions.length - 1; |
| 242 | + }); |
| 243 | + break; |
| 244 | + |
| 245 | + case 'Enter': |
| 246 | + event.preventDefault(); |
| 247 | + if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) { |
| 248 | + handleOptionClick(filteredOptions[focusedIndex]); |
| 249 | + } |
| 250 | + break; |
| 251 | + |
| 252 | + case 'Escape': |
| 253 | + setIsOpen(false); |
| 254 | + setFocusedIndex(-1); |
| 255 | + break; |
| 256 | + } |
| 257 | + }, |
| 258 | + [shouldShowMenu, filteredOptions, focusedIndex, handleOptionClick] |
| 259 | + ); |
245 | 260 |
|
246 |
| - function handleClickAway(): void { |
| 261 | + const handleClickAway = useCallback((): void => { |
247 | 262 | setIsOpen(false);
|
248 | 263 | setFocusedIndex(-1);
|
249 |
| - } |
| 264 | + }, []); |
250 | 265 |
|
251 | 266 | return (
|
252 | 267 | <ClickAwayListener onClickAway={handleClickAway}>
|
|
0 commit comments