22import React , { useState , useEffect , useRef } from 'react' ;
33import './index.css' ;
44
5+ interface AutocompleteProps {
6+ field : string ,
7+ size ?: number ,
8+ onTermSelected : ( term : string ) => void ,
9+ apiKey : string
10+ }
11+
512function Autocomplete ( {
613 field, size, onTermSelected, apiKey,
7- } ) {
14+ } : AutocompleteProps ) {
815 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
9- const [ searchResults , setSearchResults ] = useState ( [ ] ) ;
16+ const [ searchResults , setSearchResults ] = useState < { name : string , count : number } [ ] > ( [ ] ) ;
1017 const [ focus , setFocus ] = useState ( false ) ;
1118 const [ errorMessage , setErrorMessage ] = useState ( '' ) ;
1219 const [ isLoading , setIsLoading ] = useState ( false ) ;
1320 const [ isActive , setIsActive ] = useState ( 0 ) ;
1421
1522 const timer = useRef ( null ) ;
1623
17- const autoInput = document . querySelector ( '.pdl-auto-input' ) ;
18-
1924 const clearResults = ( ) => {
2025 setSearchResults ( [ ] ) ;
2126 } ;
2227
23- const fetchResults = async ( ) => {
28+ const fetchResults = async ( ) : Promise < void > => {
2429 setErrorMessage ( '' ) ;
2530 setIsActive ( 0 ) ;
2631
@@ -29,8 +34,10 @@ function Autocomplete({
2934 setIsLoading ( true ) ;
3035
3136 if ( searchTerm . length === 0 ) {
37+ const debouncedTimeout : null | ReturnType < typeof setTimeout > = timer . current ;
3238 setIsLoading ( false ) ;
33- clearTimeout ( timer . current ) ;
39+ if ( debouncedTimeout !== null ) clearTimeout ( debouncedTimeout ) ; //
40+
3441 clearResults ( ) ;
3542 return ;
3643 }
@@ -81,11 +88,12 @@ function Autocomplete({
8188 setIsLoading ( false ) ;
8289 } ;
8390
84- const debounce = ( cb , delay = 250 ) => {
85- clearTimeout ( timer . current ) ;
91+ const debounce = ( cb : ( ) => void , delay = 250 ) => {
92+ let debouncedTimeout : ReturnType < typeof setTimeout > | null = timer . current ;
93+ if ( debouncedTimeout !== null ) clearTimeout ( debouncedTimeout ) ;
8694
8795 return ( ) => {
88- timer . current = setTimeout ( ( ) => {
96+ debouncedTimeout = setTimeout ( ( ) => {
8997 cb ( ) ;
9098 } , delay ) ;
9199 } ;
@@ -97,51 +105,69 @@ function Autocomplete({
97105
98106 const blur = ( ) => {
99107 setFocus ( false ) ;
100- autoInput . blur ( ) ;
108+ const autoInput : HTMLInputElement | null = document . querySelector ( '.pdl-auto-input' ) ;
109+ if ( autoInput !== null ) { autoInput . blur ( ) ; }
101110 } ;
102111
103- const selectHandler = ( e ) => {
112+ const mouseDownHandler = ( e : React . MouseEvent ) => {
104113 if ( searchResults . length === 0 ) return ;
105114
106115 if ( e . type === 'mousedown' ) {
107- const selected = document . querySelector ( '.pdl-selected' ) ;
108- const selectedTerm = selected . getAttribute ( 'value' ) ;
109- setSearchTerm ( selectedTerm ) ;
110- onTermSelected ( selectedTerm ) ;
111- blur ( ) ;
112- return ;
113- }
116+ const selected : HTMLInputElement | null = document ?. querySelector ( '.pdl-selected' ) ;
114117
115- switch ( e . key ) {
116- case 'Enter' : {
117- const selected = document . querySelector ( '.pdl-selected' ) ;
118- const selectedTerm = selected . getAttribute ( 'value' ) ;
119- setSearchTerm ( selectedTerm ) ;
120- onTermSelected ( selectedTerm ) ;
121- blur ( ) ;
122- break ;
123- }
124- case 'ArrowDown' :
125- if ( isActive === ( searchResults . length - 1 ) || ( isActive === null ) ) {
126- setIsActive ( 0 ) ;
127- } else {
128- const toAdd = isActive ;
129- setIsActive ( toAdd + 1 ) ;
118+ if ( selected !== null ) {
119+ const selectedTerm : string | null = selected ?. getAttribute ( 'data-value' ) ;
120+
121+ if ( selectedTerm !== null ) {
122+ setSearchTerm ( selectedTerm ) ;
123+ onTermSelected ( selectedTerm ) ;
130124 }
131- break ;
132- case 'ArrowUp' :
133- if ( isActive === 0 || isActive === null ) {
134- setIsActive ( searchResults . length - 1 ) ;
135- } else {
136- const toSubtract = isActive ;
137- setIsActive ( toSubtract - 1 ) ;
125+ }
126+ }
127+ blur ( ) ;
128+ } ;
129+
130+ const keyDownHandler = ( e : React . KeyboardEvent ) => {
131+ if ( searchResults . length === 0 ) return ;
132+
133+ if ( e . type === 'keydown' ) {
134+ switch ( e . key ) {
135+ case 'Enter' : {
136+ const selected : HTMLInputElement | null = document . querySelector ( '.pdl-selected' ) ;
137+
138+ if ( selected !== null ) {
139+ const selectedTerm = selected . getAttribute ( 'data-value' ) ;
140+
141+ if ( selectedTerm !== null ) {
142+ setSearchTerm ( selectedTerm ) ;
143+ onTermSelected ( selectedTerm ) ;
144+ }
145+ }
146+ blur ( ) ;
147+ break ;
138148 }
139- break ;
140- case 'Escape' :
141- blur ( ) ;
142- break ;
143- default :
144- break ;
149+ case 'ArrowDown' :
150+ if ( isActive === ( searchResults . length - 1 ) || ( isActive === null ) ) {
151+ setIsActive ( 0 ) ;
152+ } else {
153+ const toAdd = isActive ;
154+ setIsActive ( toAdd + 1 ) ;
155+ }
156+ break ;
157+ case 'ArrowUp' :
158+ if ( isActive === 0 || isActive === null ) {
159+ setIsActive ( searchResults . length - 1 ) ;
160+ } else {
161+ const toSubtract = isActive ;
162+ setIsActive ( toSubtract - 1 ) ;
163+ }
164+ break ;
165+ case 'Escape' :
166+ blur ( ) ;
167+ break ;
168+ default :
169+ break ;
170+ }
145171 }
146172 } ;
147173
@@ -184,7 +210,7 @@ function Autocomplete({
184210 onChange = { ( e ) => setSearchTerm ( e . currentTarget . value ) }
185211 onFocus = { ( ) => setFocus ( true ) }
186212 onBlur = { ( ) => setFocus ( false ) }
187- onKeyDown = { ( e ) => selectHandler ( e ) }
213+ onKeyDown = { ( e ) => keyDownHandler ( e ) }
188214 />
189215 < div className = { `pdl-loading-spinner ${ isLoading ? '' : 'pdl-dn' } ` } />
190216 </ div >
@@ -195,10 +221,13 @@ function Autocomplete({
195221 < div
196222 key = { idx }
197223 className = { `pdl-suggestion pdl-df pdl-row ${ idx === isActive ? 'pdl-selected' : '' } ` }
198- value = { searchResult . name }
224+ data- value= { searchResult . name }
199225 data-idx = { idx }
200- onMouseOver = { ( e ) => setIsActive ( parseInt ( e . currentTarget . dataset . idx , 10 ) ) }
201- onMouseDown = { ( e ) => selectHandler ( e ) }
226+ onMouseOver = { ( e ) => {
227+ const indexString : string | undefined = e . currentTarget . dataset . idx ;
228+ if ( indexString ) setIsActive ( parseInt ( indexString , 10 ) ) ;
229+ } }
230+ onMouseDown = { ( e ) => mouseDownHandler ( e ) }
202231 >
203232 < div className = "pdl-suggestion-name" >
204233 { searchResult . name }
0 commit comments