From 07c708e9fd3c0321da9df52a72a8bd9126ea02fe Mon Sep 17 00:00:00 2001 From: rektdeckard Date: Tue, 18 Aug 2020 14:16:36 -0400 Subject: [PATCH] SearchInput: debounce input and add wait indicator --- src/components/SearchInput/SearchInput.css | 5 ++++ src/components/SearchInput/SearchInput.tsx | 29 ++++++++++++++++------ src/state/selectors.ts | 6 ++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/components/SearchInput/SearchInput.css b/src/components/SearchInput/SearchInput.css index f86e121..83e64f7 100644 --- a/src/components/SearchInput/SearchInput.css +++ b/src/components/SearchInput/SearchInput.css @@ -43,3 +43,8 @@ .clear-icon { cursor: pointer; } + +.wait-icon { + cursor: wait; + /* opacity: 0.5; */ +} diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx index ec1760e..7da8b6b 100644 --- a/src/components/SearchInput/SearchInput.tsx +++ b/src/components/SearchInput/SearchInput.tsx @@ -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 = () => { + const [value, setValue] = useState(""); const [query, setQuery] = useRecoilState(searchQueryAtom); + void(query); - const handleSearchChange = (event: React.ChangeEvent) => { - 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 (
@@ -21,11 +30,17 @@ const SearchInput: React.FC = () => { 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 && setQuery("")} />} + {value ? ( + isReady() ? ( + + ) : ( + + ) + ) : null}
); }; diff --git a/src/state/selectors.ts b/src/state/selectors.ts index ac3a09e..46c59a3 100644 --- a/src/state/selectors.ts +++ b/src/state/selectors.ts @@ -20,7 +20,7 @@ export const filteredQueryResultsSelector = selector>({ 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((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)) )