refactor: add useDebounce hook and replace manual debounce in admin search
- Create reusable useDebounce<T> hook in frontend/src/hooks/useDebounce.ts - Replace manual setTimeout/clearTimeout + extra state in AdminAppsPage with clean useDebounce(search, 400) pattern - Removes timer state and handleSearch wrapper function
This commit is contained in:
@@ -8,6 +8,7 @@ import { toast } from 'react-toastify';
|
||||
import type { Application } from '@/types';
|
||||
import { Search, X, Package, Hexagon, User, Database, Box } from 'lucide-react';
|
||||
import { useConfirm } from '@/components/confirm-modal';
|
||||
import { useDebounce } from '@/hooks/useDebounce';
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
running: 'badge-green',
|
||||
@@ -23,15 +24,7 @@ export default function AdminAppsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const confirm = useConfirm();
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearch(value);
|
||||
if (timer) clearTimeout(timer);
|
||||
const t = setTimeout(() => setDebouncedSearch(value), 400);
|
||||
setTimer(t);
|
||||
};
|
||||
const debouncedSearch = useDebounce(search, 400);
|
||||
|
||||
const { data: apps = [], isLoading } = useQuery<Application[]>({
|
||||
queryKey: ['admin-applications', debouncedSearch],
|
||||
@@ -138,12 +131,12 @@ export default function AdminAppsPage() {
|
||||
type="text"
|
||||
placeholder="Search by app name, user name, email, or user ID…"
|
||||
value={search}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
onClick={() => { setSearch(''); setDebouncedSearch(''); }}
|
||||
onClick={() => setSearch('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Debounce a value by a given delay.
|
||||
*
|
||||
* @param value The raw (fast-changing) value, e.g. a search input string.
|
||||
* @param delay Debounce delay in milliseconds (default 400ms).
|
||||
* @returns The debounced value – only updates after the user stops
|
||||
* changing `value` for `delay` ms.
|
||||
*
|
||||
* @example
|
||||
* const [search, setSearch] = useState('');
|
||||
* const debouncedSearch = useDebounce(search, 400);
|
||||
*
|
||||
* // use `debouncedSearch` in your query key / API call
|
||||
*/
|
||||
export function useDebounce<T>(value: T, delay = 400): T {
|
||||
const [debounced, setDebounced] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebounced(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debounced;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user