From 967fa3d0a58b4a2d33ee9486e137cb6627234069 Mon Sep 17 00:00:00 2001 From: Spaully Date: Thu, 15 Oct 2026 17:43:59 +0100 Subject: [PATCH] perf(property-search): stabilize handlers and memoize search box --- src/components/PropertySearch.tsx | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/components/PropertySearch.tsx b/src/components/PropertySearch.tsx index d0636906..e81bb94a 100644 --- a/src/components/PropertySearch.tsx +++ b/src/components/PropertySearch.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useRef, useEffect, useId } from 'react'; +import React, { memo, useCallback, useState, useRef, useEffect, useId } from 'react'; import { useDebounce } from '@/hooks/useDebounce'; import { usePropertyAutocompleteQuery } from '@/hooks/usePropertySearchQuery'; import { useSearchHistory } from '@/hooks/useSearchHistory'; @@ -14,7 +14,7 @@ interface PropertySearchProps { placeholder?: string; } -export const PropertySearch = ({ +const PropertySearchInner = ({ value, onChange, placeholder = 'Search properties, locations...', @@ -50,35 +50,35 @@ export const PropertySearch = ({ return () => document.removeEventListener('mousedown', handleClickOutside); }, []); - const handleInputChange = (e: React.ChangeEvent) => { + const handleInputChange = useCallback((e: React.ChangeEvent) => { onChange(e.target.value); setSelectedIndex(-1); if (e.target.value.length > 2) { setShowHistory(false); } - }; + }, [onChange]); - const handleSuggestionClick = (suggestion: AutocompleteResult) => { + const handleSuggestionClick = useCallback((suggestion: AutocompleteResult) => { onChange(suggestion.value); setIsFocused(false); saveToHistory(suggestion.value, suggestion.type === 'property' ? 'property_type' : 'location'); - }; + }, [onChange, saveToHistory]); - const handleSearchSelect = (query: string) => { + const handleSearchSelect = useCallback((query: string) => { onChange(query); setIsFocused(false); setShowHistory(false); saveToHistory(query); - }; + }, [onChange, saveToHistory]); - const handleFocus = () => { + const handleFocus = useCallback(() => { setIsFocused(true); if (value.length <= 2) { setShowHistory(true); } - }; + }, [value]); - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (!suggestions.length) return; switch (e.key) { @@ -105,12 +105,12 @@ export const PropertySearch = ({ setIsFocused(false); break; } - }; + }, [suggestions, selectedIndex, handleSuggestionClick]); - const handleClear = () => { + const handleClear = useCallback(() => { onChange(''); inputRef.current?.focus(); - }; + }, [onChange]); const showDropdown = isFocused && (suggestions.length > 0 || isLoading || showHistory); const activeOptionId = selectedIndex >= 0 ? `${listboxId}-option-${selectedIndex}` : undefined; @@ -234,3 +234,9 @@ export const PropertySearch = ({ ); }; + +/** + * Memoized so parent re-renders that pass an unchanged `value`, `onChange` and + * `placeholder` skip re-rendering the search box and its suggestion list. + */ +export const PropertySearch = memo(PropertySearchInner);