1- import React , { useCallback , useContext , useEffect , useRef } from "react" ;
1+ import React , { useCallback , useContext , useEffect , useRef , useState } from "react" ;
22import { useLocation , useNavigate } from "react-router-dom" ;
33import styled from "styled-components" ;
44import { SearchContext } from "./SearchContext" ;
@@ -46,32 +46,45 @@ export const SearchInput = styled.input.attrs({ type: "search" })`
4646
4747export function SearchBox ( { baseUrl, className, placeholder = "Search..." } : { baseUrl : string ; className ?: string ; placeholder ?: string } ) {
4848 const { search, setSearch } = useContext ( SearchContext ) ;
49+ const [ inputValue , setInputValue ] = useState ( search ) ;
4950 const navigate = useNavigate ( ) ;
5051 const location = useLocation ( ) ;
5152 const timerRef = useRef < ReturnType < typeof setTimeout > > ( undefined ) ;
53+ const pendingRef = useRef ( inputValue ) ;
5254
53- // Sync context from URL (back/forward navigation, clicking links)
55+ // Sync from URL (back/forward navigation, clicking links)
5456 useEffect ( ( ) => {
5557 const urlSearch = new URLSearchParams ( location . search ) . get ( "search" ) ?? "" ;
5658 if ( urlSearch !== search ) {
5759 setSearch ( urlSearch ) ;
60+ setInputValue ( urlSearch ) ;
61+ pendingRef . current = urlSearch ;
5862 }
5963 } , [ location . search ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
6064
65+ // Sync input when search is set externally (e.g. from programmatic navigation)
66+ useEffect ( ( ) => {
67+ setInputValue ( search ) ;
68+ pendingRef . current = search ;
69+ } , [ search ] ) ;
70+
6171 // Cleanup debounce timer on unmount
6272 useEffect ( ( ) => ( ) => clearTimeout ( timerRef . current ) , [ ] ) ;
6373
6474 const onChange = useCallback < React . ChangeEventHandler < HTMLInputElement > > (
6575 ( { target : { value } } ) => {
66- setSearch ( value ) ;
76+ setInputValue ( value ) ;
77+ pendingRef . current = value ;
6778 clearTimeout ( timerRef . current ) ;
6879 timerRef . current = setTimeout ( ( ) => {
69- if ( value === "" ) {
80+ const latest = pendingRef . current ;
81+ setSearch ( latest ) ;
82+ if ( latest === "" ) {
7083 navigate ( baseUrl , { replace : true } ) ;
7184 } else {
72- navigate ( `${ baseUrl } ?search=${ encodeURIComponent ( value ) } ` , { replace : true } ) ;
85+ navigate ( `${ baseUrl } ?search=${ encodeURIComponent ( latest ) } ` , { replace : true } ) ;
7386 }
74- } , 300 ) ;
87+ } , 150 ) ;
7588 } ,
7689 [ setSearch , navigate , baseUrl ] ,
7790 ) ;
@@ -83,7 +96,7 @@ export function SearchBox({ baseUrl, className, placeholder = "Search..." }: { b
8396 className = { className }
8497 placeholder = { placeholder }
8598 ref = { ref }
86- value = { search }
99+ value = { inputValue }
87100 onChange = { onChange }
88101 aria-label = "Search"
89102 />
0 commit comments