mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
refactor and optimize search, add RQ for search
This commit is contained in:
parent
3d0bfaef51
commit
61cb2858bb
13 changed files with 71 additions and 102 deletions
|
|
@ -3,7 +3,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Input } from '~/components/ui/Input.tsx';
|
import { Input } from '~/components/ui/Input.tsx';
|
||||||
import { Label } from '~/components/ui/Label.tsx';
|
import { Label } from '~/components/ui/Label.tsx';
|
||||||
import { Checkbox } from '~/components/ui/Checkbox.tsx';
|
import { Checkbox } from '~/components/ui/Checkbox.tsx';
|
||||||
import SelectDropdown from '../../ui/SelectDropDown';
|
import SelectDropDown from '../../ui/SelectDropDown';
|
||||||
import { axiosPost } from '~/utils/fetchers.js';
|
import { axiosPost } from '~/utils/fetchers.js';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
@ -62,7 +62,7 @@ function Settings(props) {
|
||||||
>
|
>
|
||||||
Tone Style <small className="opacity-40">(default: fast)</small>
|
Tone Style <small className="opacity-40">(default: fast)</small>
|
||||||
</Label>
|
</Label>
|
||||||
<SelectDropdown
|
<SelectDropDown
|
||||||
id="toneStyle-dropdown"
|
id="toneStyle-dropdown"
|
||||||
title={null}
|
title={null}
|
||||||
value={`${toneStyle.charAt(0).toUpperCase()}${toneStyle.slice(1)}`}
|
value={`${toneStyle.charAt(0).toUpperCase()}${toneStyle.slice(1)}`}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import SelectDropdown from '../../ui/SelectDropDown';
|
import SelectDropDown from '../../ui/SelectDropDown';
|
||||||
import { Input } from '~/components/ui/Input.tsx';
|
import { Input } from '~/components/ui/Input.tsx';
|
||||||
import { Label } from '~/components/ui/Label.tsx';
|
import { Label } from '~/components/ui/Label.tsx';
|
||||||
import { Slider } from '~/components/ui/Slider.tsx';
|
import { Slider } from '~/components/ui/Slider.tsx';
|
||||||
|
|
@ -38,7 +38,7 @@ function Settings(props) {
|
||||||
<div className="grid gap-6 sm:grid-cols-2">
|
<div className="grid gap-6 sm:grid-cols-2">
|
||||||
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
|
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<SelectDropdown
|
<SelectDropDown
|
||||||
value={model}
|
value={model}
|
||||||
setValue={setModel}
|
setValue={setModel}
|
||||||
availableValues={models}
|
availableValues={models}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { cn } from '~/utils';
|
||||||
import { Button } from '../../ui/Button.tsx';
|
import { Button } from '../../ui/Button.tsx';
|
||||||
import { Settings2 } from 'lucide-react';
|
import { Settings2 } from 'lucide-react';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx';
|
import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx';
|
||||||
import SelectDropdown from '../../ui/SelectDropDown';
|
import SelectDropDown from '../../ui/SelectDropDown';
|
||||||
import Settings from '../../Endpoints/BingAI/Settings.jsx';
|
import Settings from '../../Endpoints/BingAI/Settings.jsx';
|
||||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||||
|
|
@ -68,7 +68,7 @@ function BingAIOptions() {
|
||||||
(!advancedMode ? ' show' : '')
|
(!advancedMode ? ' show' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectDropdown
|
<SelectDropDown
|
||||||
title="Mode"
|
title="Mode"
|
||||||
value={jailbreak ? 'Sydney' : 'BingAI'}
|
value={jailbreak ? 'Sydney' : 'BingAI'}
|
||||||
setValue={value => setOption('jailbreak')(value === 'Sydney')}
|
setValue={value => setOption('jailbreak')(value === 'Sydney')}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import SelectDropdown from '../../ui/SelectDropDown.jsx';
|
import SelectDropDown from '../../ui/SelectDropDown';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
@ -41,7 +41,7 @@ function ChatGPTOptions() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="openAIOptions-simple-container show flex w-full flex-wrap items-center justify-center gap-2">
|
<div className="openAIOptions-simple-container show flex w-full flex-wrap items-center justify-center gap-2">
|
||||||
<SelectDropdown
|
<SelectDropDown
|
||||||
value={model}
|
value={model}
|
||||||
setValue={setOption('model')}
|
setValue={setOption('model')}
|
||||||
availableValues={models}
|
availableValues={models}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Settings2 } from 'lucide-react';
|
import { Settings2 } from 'lucide-react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import SelectDropdown from '../../ui/SelectDropDown';
|
import SelectDropDown from '../../ui/SelectDropDown';
|
||||||
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||||
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||||
import { Button } from '../../ui/Button.tsx';
|
import { Button } from '../../ui/Button.tsx';
|
||||||
|
|
@ -89,7 +89,7 @@ function OpenAIOptions() {
|
||||||
' z-50 flex h-[40px] items-center justify-center px-4 hover:bg-slate-50 data-[state=open]:bg-slate-50 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600'
|
' z-50 flex h-[40px] items-center justify-center px-4 hover:bg-slate-50 data-[state=open]:bg-slate-50 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600'
|
||||||
)}
|
)}
|
||||||
/> */}
|
/> */}
|
||||||
<SelectDropdown
|
<SelectDropDown
|
||||||
value={model}
|
value={model}
|
||||||
setValue={setOption('model')}
|
setValue={setOption('model')}
|
||||||
availableValues={models}
|
availableValues={models}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
import React from 'react';
|
|
||||||
import SearchBar from './SearchBar';
|
import SearchBar from './SearchBar';
|
||||||
import ClearConvos from './ClearConvos';
|
import ClearConvos from './ClearConvos';
|
||||||
import DarkMode from './DarkMode';
|
import DarkMode from './DarkMode';
|
||||||
import Logout from './Logout';
|
import Logout from './Logout';
|
||||||
import ExportConversation from './ExportConversation';
|
import ExportConversation from './ExportConversation';
|
||||||
|
|
||||||
export default function NavLinks({ fetch, onSearchSuccess, clearSearch, isSearchEnabled }) {
|
export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!!isSearchEnabled && (
|
{!!isSearchEnabled && (
|
||||||
<SearchBar
|
<SearchBar
|
||||||
fetch={fetch}
|
|
||||||
onSuccess={onSearchSuccess}
|
|
||||||
clearSearch={clearSearch}
|
clearSearch={clearSearch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,29 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function SearchBar({ fetch, clearSearch }) {
|
export default function SearchBar({ clearSearch }) {
|
||||||
// const dispatch = useDispatch();
|
|
||||||
const [inputValue, setInputValue] = useState('');
|
|
||||||
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
|
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
|
||||||
|
|
||||||
// const [inputValue, setInputValue] = useState('');
|
|
||||||
|
|
||||||
const debouncedChangeHandler = useCallback(
|
|
||||||
debounce(q => {
|
|
||||||
setSearchQuery(q);
|
|
||||||
}, 750),
|
|
||||||
[setSearchQuery]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (searchQuery.length > 0) {
|
|
||||||
fetch(searchQuery, 1);
|
|
||||||
setInputValue(searchQuery);
|
|
||||||
}
|
|
||||||
}, [searchQuery]);
|
|
||||||
|
|
||||||
const handleKeyUp = e => {
|
const handleKeyUp = e => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
if (e.keyCode === 8 && value === '') {
|
if (e.keyCode === 8 && value === '') {
|
||||||
// Value after clearing input: ""
|
|
||||||
console.log(`Value after clearing input: "${value}"`);
|
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
clearSearch();
|
clearSearch();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeHandler = e => {
|
|
||||||
let q = e.target.value;
|
|
||||||
setInputValue(q);
|
|
||||||
q = q.trim();
|
|
||||||
|
|
||||||
if (q === '') {
|
|
||||||
setSearchQuery('');
|
|
||||||
clearSearch();
|
|
||||||
} else {
|
|
||||||
debouncedChangeHandler(q);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10">
|
<div className="flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10">
|
||||||
{<Search className="h-4 w-4" />}
|
{<Search className="h-4 w-4" />}
|
||||||
<input
|
<input
|
||||||
// ref={inputRef}
|
|
||||||
type="text"
|
type="text"
|
||||||
className="m-0 mr-0 w-full border-none bg-transparent p-0 text-sm leading-tight outline-none"
|
className="m-0 mr-0 w-full border-none bg-transparent p-0 text-sm leading-tight outline-none"
|
||||||
value={inputValue}
|
value={searchQuery}
|
||||||
onChange={changeHandler}
|
onChange={e => setSearchQuery(e.target.value)}
|
||||||
placeholder="Search messages"
|
placeholder="Search messages"
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
// onBlur={onRename}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import _ from 'lodash';
|
|
||||||
import NewChat from './NewChat';
|
import NewChat from './NewChat';
|
||||||
import Spinner from '../svg/Spinner';
|
import Spinner from '../svg/Spinner';
|
||||||
import Pages from '../Conversations/Pages';
|
import Pages from '../Conversations/Pages';
|
||||||
import Conversations from '../Conversations';
|
import Conversations from '../Conversations';
|
||||||
import NavLinks from './NavLinks';
|
import NavLinks from './NavLinks';
|
||||||
import { searchFetcher } from '~/utils/fetchers';
|
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { useGetConversationsQuery } from '~/data-provider';
|
import { useGetConversationsQuery, useSearchQuery } from '~/data-provider';
|
||||||
|
import useDebounce from '~/hooks/useDebounce';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function Nav({ navVisible, setNavVisible }) {
|
export default function Nav({ navVisible, setNavVisible }) {
|
||||||
|
|
@ -17,7 +15,6 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const scrollPositionRef = useRef(null);
|
const scrollPositionRef = useRef(null);
|
||||||
|
|
||||||
// const dispatch = useDispatch();
|
|
||||||
const [conversations, setConversations] = useState([]);
|
const [conversations, setConversations] = useState([]);
|
||||||
// current page
|
// current page
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
|
|
@ -43,6 +40,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const [isFetching, setIsFetching] = useState(false);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
const onSearchSuccess = (data, expectedPage) => {
|
const onSearchSuccess = (data, expectedPage) => {
|
||||||
|
console.log('onSearchSuccess', data, expectedPage)
|
||||||
const res = data;
|
const res = data;
|
||||||
setConversations(res.conversations);
|
setConversations(res.conversations);
|
||||||
if (expectedPage) {
|
if (expectedPage) {
|
||||||
|
|
@ -55,14 +53,22 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
setSearchResultMessages(res.messages);
|
setSearchResultMessages(res.messages);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: dont need this
|
const debouncedSearchTerm = useDebounce(searchQuery, 750);
|
||||||
const fetch = useCallback(
|
const searchQueryFn = useSearchQuery(debouncedSearchTerm, 1, {
|
||||||
_.partialRight(
|
enabled: !!debouncedSearchTerm &&
|
||||||
searchFetcher.bind(null, () => setIsFetching(true)),
|
debouncedSearchTerm.length > 0 &&
|
||||||
onSearchSuccess
|
isSearchEnabled &&
|
||||||
),
|
isSearching,
|
||||||
[setIsFetching]
|
});
|
||||||
);
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQueryFn.isInitialLoading) {
|
||||||
|
setIsFetching(true);
|
||||||
|
}
|
||||||
|
else if (searchQueryFn.data) {
|
||||||
|
onSearchSuccess(searchQueryFn.data);
|
||||||
|
}
|
||||||
|
}, [searchQueryFn.data, searchQueryFn.isInitialLoading])
|
||||||
|
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
setPageNumber(1);
|
setPageNumber(1);
|
||||||
|
|
@ -178,8 +184,6 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NavLinks
|
<NavLinks
|
||||||
fetch={fetch}
|
|
||||||
onSearchSuccess={onSearchSuccess}
|
|
||||||
clearSearch={clearSearch}
|
clearSearch={clearSearch}
|
||||||
isSearchEnabled={isSearchEnabled}
|
isSearchEnabled={isSearchEnabled}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import CheckMark from '../svg/CheckMark.jsx';
|
||||||
import { Listbox, Transition } from '@headlessui/react';
|
import { Listbox, Transition } from '@headlessui/react';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
function SelectDropdown({
|
function SelectDropDown({
|
||||||
title = 'Model',
|
title = 'Model',
|
||||||
value,
|
value,
|
||||||
disabled,
|
disabled,
|
||||||
|
|
@ -111,4 +111,4 @@ function SelectDropdown({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SelectDropdown;
|
export default SelectDropDown;
|
||||||
|
|
|
||||||
|
|
@ -49,35 +49,14 @@ export function getSearchEnabled(): Promise<boolean> {
|
||||||
return request.get(endpoints.searchEnabled());
|
return request.get(endpoints.searchEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSearchResults(q: string, pageNumber: string): Promise<t.TSearchResults> {
|
|
||||||
return request.get(endpoints.search(q, pageNumber));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUser(): Promise<t.TUser> {
|
export function getUser(): Promise<t.TUser> {
|
||||||
return request.get(endpoints.user());
|
return request.get(endpoints.user());
|
||||||
}
|
}
|
||||||
|
|
||||||
type TSearchFetcherProps = {
|
export const searchConversations = async(q: string, pageNumber: string): Promise<t.TSearchResults> => {
|
||||||
pre: () => void,
|
return request.get(endpoints.search(q, pageNumber));
|
||||||
q: string,
|
|
||||||
pageNumber: string,
|
|
||||||
callback: (data: any) => void
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchConversations = async({ q, pageNumber, callback }: TSearchFetcherProps) => {
|
|
||||||
return request.get(endpoints.search(q, pageNumber)).then(({ data }) => {
|
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchFetcher = async ({ pre, q, pageNumber, callback }: TSearchFetcherProps) => {
|
|
||||||
pre();
|
|
||||||
//@ts-ignore
|
|
||||||
const { data } = await request.get(endpoints.search(q, pageNumber));
|
|
||||||
console.log('search data', data);
|
|
||||||
callback(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAIEndpoints = () => {
|
export const getAIEndpoints = () => {
|
||||||
return request.get(endpoints.aiEndpoints());
|
return request.get(endpoints.aiEndpoints());
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +120,6 @@ export const useClearConversationsMutation = (): UseMutationResult<unknown> => {
|
||||||
export const useGetConversationsQuery = (pageNumber: string): QueryObserverResult<t.Conversation[]> => {
|
export const useGetConversationsQuery = (pageNumber: string): QueryObserverResult<t.Conversation[]> => {
|
||||||
return useQuery([QueryKeys.allConversations, pageNumber], () =>
|
return useQuery([QueryKeys.allConversations, pageNumber], () =>
|
||||||
dataService.getConversations(pageNumber), {
|
dataService.getConversations(pageNumber), {
|
||||||
// refetchOnWindowFocus: false,
|
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
}
|
}
|
||||||
|
|
@ -191,9 +190,14 @@ export const useDeleteAllPresetsMutation = (): UseMutationResult<unknown> => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSearchFetcher = (query: string, pageNumber: string, callback: () => void, config?: UseQueryOptions<t.TSearchResponse>): QueryObserverResult<t.TSearchResponse> => {
|
export const useSearchQuery = (
|
||||||
return useQuery<t.TSearchResponse>([QueryKeys.searchResults, pageNumber, query], () =>
|
searchQuery: string,
|
||||||
dataService.searchConversations(query, pageNumber, callback), {
|
pageNumber: string,
|
||||||
|
config?: UseQueryOptions<t.TSearchResults>
|
||||||
|
): QueryObserverResult<t.TSearchResults> => {
|
||||||
|
console.log('useSearchFetcher', searchQuery, pageNumber)
|
||||||
|
return useQuery<t.TSearchResponse>([QueryKeys.searchResults, pageNumber, searchQuery], () =>
|
||||||
|
dataService.searchConversations(searchQuery, pageNumber), {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export type TDeleteConversationResponse = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSearchResponse = {
|
export type TSearchResults = {
|
||||||
conversations: TConversation[],
|
conversations: TConversation[],
|
||||||
messages: TMessage[],
|
messages: TMessage[],
|
||||||
pageNumber: string,
|
pageNumber: string,
|
||||||
|
|
|
||||||
22
client/src/hooks/useDebounce.js
Normal file
22
client/src/hooks/useDebounce.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function useDebounce(value, delay) {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[value, delay]
|
||||||
|
);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useDebounce;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue