SearchInput: debounce input and add wait indicator

This commit is contained in:
rektdeckard
2020-08-18 14:16:36 -04:00
parent e1c4a72026
commit 07c708e9fd
3 changed files with 30 additions and 10 deletions

View File

@@ -43,3 +43,8 @@
.clear-icon {
cursor: pointer;
}
.wait-icon {
cursor: wait;
/* opacity: 0.5; */
}

View File

@@ -1,6 +1,7 @@
import React from "react";
import React, { useState } from "react";
import { useRecoilState } from "recoil";
import { MagnifyingGlass, X } from "phosphor-react";
import { useDebounce } from "react-use";
import { MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
import { searchQueryAtom } from "../../state/atoms";
import "./SearchInput.css";
@@ -8,11 +9,19 @@ import "./SearchInput.css";
type SearchInputProps = {};
const SearchInput: React.FC<SearchInputProps> = () => {
const [value, setValue] = useState<string>("");
const [query, setQuery] = useRecoilState(searchQueryAtom);
void(query);
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
const [isReady] = useDebounce(() => setQuery(value), 250, [value]);
const handleCancelSearch = () => {
setValue("");
// Should cancel pending debounce timeouts and immediately clear query
// without causing lag!
// setQuery("");
};
return (
<div className="search-bar">
<MagnifyingGlass size={24} />
@@ -21,11 +30,17 @@ const SearchInput: React.FC<SearchInputProps> = () => {
aria-label="Search for an icon"
type="text"
autoComplete="off"
value={query}
value={value}
placeholder="Search for an icon"
onChange={handleSearchChange}
onChange={({ currentTarget }) => setValue(currentTarget.value)}
/>
{query && <X className="clear-icon" size={18} onClick={() => setQuery("")} />}
{value ? (
isReady() ? (
<X className="clear-icon" size={18} onClick={handleCancelSearch} />
) : (
<HourglassHigh className="wait-icon" weight="fill" size={18} />
)
) : null}
</div>
);
};

View File

@@ -20,7 +20,7 @@ export const filteredQueryResultsSelector = selector<Readonly<IconEntry[]>>({
if (!query && !style) return icons;
return await new Promise((resolve) =>
return new Promise((resolve) =>
resolve(icons.filter((icon) => isQueryMatch(icon, query)))
);
},
@@ -34,7 +34,7 @@ export const categorizedQueryResultsSelector = selector<
key: "categorizedQueryResultsSelector",
get: async ({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return await new Promise((resolve) =>
return new Promise((resolve) =>
resolve(
filteredResults.reduce<CategorizedIcons>((acc, curr) => {
curr.categories.forEach((category) => {
@@ -55,7 +55,7 @@ export const singleCategoryQueryResultsSelector = selectorFamily<
key: "singleCategoryQueryResultsSelector",
get: (category: IconCategory) => async ({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return await new Promise((resolve) =>
return new Promise((resolve) =>
resolve(
filteredResults.filter((icon) => icon.categories.includes(category))
)