I wanted to create a search function in my app.
When it comes to search function, I personally like the search bar that I don't need to click a search button after I finish typing.
In order to implement the search function, where search result will be displayed automatically on the app UI after user has finished typing, I learned how to use 'useDebounce' custom hook.
useDebounce?
It has a simple function that returns the value after arbitrary duration of time.
(install useBounce library.
https://www.npmjs.com/package/use-debounce)
set up state and useDebounce
The useDebounce hook takes two arguments: the value to debounce and the debounce delay time in milliseconds (700ms in this case below).
const [searchText, setSearchText] = React.useState('');
const [searchTextQuery] = useDebounce(searchText, 700);
Search input
The search bar input is like this below.
So on every keystroke, searchText state changes but searchTextQuery value won't get updated until user has stopped typing for 700ms.
<input
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="search..."
type="text"
className="w-[250px] h-[30px] mr-sm p-sm rounded-md"
/>
Query using bounced searchText value
Since I'm using GraphQl, it takes an argument called skip where I put searchTextQuery, meaning as long as searchTextQuery is empty, it won't fetch data from the database.
const {
data: piecesData,
loading: piecesLoading,
error: piecesError,
} = useQuery(SEARCH_PIECESS_QUERY, {
variables: {
userId,
searchText: searchTextQuery,
},
skip: !searchTextQuery,
});
useEffect to set PieceSearchResult state
Finally I set up the useEffect.
After searchTextQuery is returned from useDebounce, if there is fetched piecesData, it will update the pieceSearchResult state
React.useEffect(() => {
if (piecesData && !piecesError && !piecesLoading) {
setPieceSearchResult(piecesData.pieces_search);
}
}, [
searchTextQuery,
piecesData,
piecesError,
piecesLoading,
]);
This will update the search result display section UI below
<div className="grid grid-cols-4 gap-x-4 gap-y-5 w-full">
{piecesLoading && <Loading size="small"></Loading>}
{!piecesLoading && pieceSearchResult?.length === 0 && (
<div className="text-sm h-10">No search result</div>)}
{pieceSearchResult?.map((piece) => {
return (
<div key={piece.id}>
<Link href={`/piece/${piece.id}`} onClick={handleModalClose}>
<div className="relative w-[100%] aspect-[2/3] rounded-md overflow-hidden mb-1">
<Image alt="piece image" src={piece.imageUrl} fill objectFit="cover" />
</div>
</Link>
<div className="text-sm">{piece.title}</div>
</div>);
})}
</div>
Important note:
If you put both search input and search result display section in a modal, make sure you pass them as a children like below, so that the whole Modal component won't get re-rendered.
<ModalComponent>
<input/>
<searchResultDisplaySection/>
</ModalComponent>
Cover image:
from Unsplash
taken by Kenny Eliason
Top comments (0)