diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
index feb5a9c0e1..8776fa888b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
@@ -12,7 +12,8 @@ import {
useDisclosure,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { galleryViewChanged, searchTermChanged } from 'features/gallery/store/gallerySlice';
+import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
+import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
import type { CSSProperties } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -41,8 +42,9 @@ export const Gallery = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const galleryView = useAppSelector((s) => s.gallery.galleryView);
- const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
- const searchDisclosure = useDisclosure({ defaultIsOpen: !!searchTerm.length });
+ const initialSearchTerm = useAppSelector((s) => s.gallery.searchTerm);
+ const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 });
+ const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm();
const handleClickImages = useCallback(() => {
dispatch(galleryViewChanged('images'));
@@ -53,13 +55,9 @@ export const Gallery = () => {
}, [dispatch]);
const handleClickSearch = useCallback(() => {
- if (searchTerm.length) {
- dispatch(searchTermChanged(''));
- searchDisclosure.onToggle();
- } else {
- searchDisclosure.onToggle();
- }
- }, [searchTerm, dispatch, searchDisclosure]);
+ searchDisclosure.onToggle();
+ onResetSearchTerm();
+ }, [onResetSearchTerm, searchDisclosure]);
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
const boardName = useBoardName(selectedBoardId);
@@ -92,7 +90,11 @@ export const Gallery = () => {
-
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx
index d4f546e1ed..3990495106 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx
@@ -1,51 +1,37 @@
import { IconButton, Input, InputGroup, InputRightElement, Spinner } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAppSelector } from 'app/store/storeHooks';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
-import { searchTermChanged } from 'features/gallery/store/gallerySlice';
-import { debounce } from 'lodash-es';
import type { ChangeEvent } from 'react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
import { useListImagesQuery } from 'services/api/endpoints/images';
-export const GallerySearch = () => {
- const dispatch = useAppDispatch();
- const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
+type Props = {
+ searchTerm: string;
+ onChangeSearchTerm: (value: string) => void;
+ onResetSearchTerm: () => void;
+};
+
+export const GallerySearch = ({ searchTerm, onChangeSearchTerm, onResetSearchTerm }: Props) => {
const { t } = useTranslation();
- const [searchTermInput, setSearchTermInput] = useState(searchTerm);
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { isPending } = useListImagesQuery(queryArgs, {
selectFromResult: ({ isLoading, isFetching }) => ({ isPending: isLoading || isFetching }),
});
- const debouncedSetSearchTerm = useMemo(() => {
- return debounce((value: string) => {
- dispatch(searchTermChanged(value));
- }, 1000);
- }, [dispatch]);
const handleChangeInput = useCallback(
(e: ChangeEvent) => {
- setSearchTermInput(e.target.value);
- debouncedSetSearchTerm(e.target.value);
+ onChangeSearchTerm(e.target.value);
},
- [debouncedSetSearchTerm]
+ [onChangeSearchTerm]
);
- const handleClearInput = useCallback(() => {
- setSearchTermInput('');
- dispatch(searchTermChanged(''));
- }, [dispatch]);
-
- useEffect(() => {
- setSearchTermInput(searchTerm);
- }, [searchTerm]);
-
return (
@@ -54,10 +40,10 @@ export const GallerySearch = () => {
)}
- {!isPending && searchTermInput.length && (
+ {!isPending && searchTerm.length && (
{
+ // Highlander!
+ useAssertSingleton('gallery-search-state');
+
+ const dispatch = useAppDispatch();
+ const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
+
+ const [localSearchTerm, setLocalSearchTerm] = useState(searchTerm);
+
+ const debouncedSetSearchTerm = useMemo(() => {
+ return debounce((val: string) => {
+ dispatch(searchTermChanged(val));
+ }, 1000);
+ }, [dispatch]);
+
+ const onChange = useCallback(
+ (val: string) => {
+ setLocalSearchTerm(val);
+ debouncedSetSearchTerm(val);
+ },
+ [debouncedSetSearchTerm]
+ );
+
+ const onReset = useCallback(() => {
+ debouncedSetSearchTerm.cancel();
+ setLocalSearchTerm('');
+ dispatch(searchTermChanged(''));
+ }, [debouncedSetSearchTerm, dispatch]);
+
+ return [localSearchTerm, onChange, onReset] as const;
+};