// src/components/ui/SearchBar.jsx // Debounced search input with a magnifier icon and animated clear button. // // Props: // value — string — controlled value (optional; uncontrolled if omitted) // onChange — (value: string) => void — called after debounce delay // placeholder — string (default: 'Search…') // debounce — number — ms delay before onChange fires (default: 300) // disabled — boolean // className — extra classes on the wrapper import { useState, useEffect, useRef } from 'react' export default function SearchBar({ value: controlledValue, onChange, placeholder = 'Search…', debounce = 300, disabled = false, className = '', }) { const isControlled = controlledValue !== undefined const [localValue, setLocalValue] = useState(isControlled ? controlledValue : '') const timerRef = useRef(null) const inputRef = useRef(null) const prevControlledRef = useRef(controlledValue) // Only sync from outside when the parent *explicitly* changes the value // (e.g. "Clear filters" resets it to ''). Do NOT sync on every re-render — // that's what caused the keystroke lag: the debounced parent setState was // writing back into this input and overwriting what the user just typed. useEffect(() => { if (isControlled && controlledValue !== prevControlledRef.current) { prevControlledRef.current = controlledValue setLocalValue(controlledValue) } }, [controlledValue, isControlled]) function handleChange(e) { const v = e.target.value setLocalValue(v) prevControlledRef.current = v // keep ref in sync so the effect doesn't clobber it if (debounce > 0) { clearTimeout(timerRef.current) timerRef.current = setTimeout(() => onChange?.(v), debounce) } else { onChange?.(v) } } function handleClear() { setLocalValue('') prevControlledRef.current = '' clearTimeout(timerRef.current) onChange?.('') inputRef.current?.focus() } const displayValue = localValue return (
{/* Search icon */} {/* Clear button — only when there's text */} {displayValue && !disabled && ( )}
) }