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 type { Application } from '@/types';
|
||||||
import { Search, X, Package, Hexagon, User, Database, Box } from 'lucide-react';
|
import { Search, X, Package, Hexagon, User, Database, Box } from 'lucide-react';
|
||||||
import { useConfirm } from '@/components/confirm-modal';
|
import { useConfirm } from '@/components/confirm-modal';
|
||||||
|
import { useDebounce } from '@/hooks/useDebounce';
|
||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
running: 'badge-green',
|
running: 'badge-green',
|
||||||
@@ -23,15 +24,7 @@ export default function AdminAppsPage() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
const debouncedSearch = useDebounce(search, 400);
|
||||||
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 { data: apps = [], isLoading } = useQuery<Application[]>({
|
const { data: apps = [], isLoading } = useQuery<Application[]>({
|
||||||
queryKey: ['admin-applications', debouncedSearch],
|
queryKey: ['admin-applications', debouncedSearch],
|
||||||
@@ -138,12 +131,12 @@ export default function AdminAppsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by app name, user name, email, or user ID…"
|
placeholder="Search by app name, user name, email, or user ID…"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
className="input pl-10 w-full"
|
className="input pl-10 w-full"
|
||||||
/>
|
/>
|
||||||
{search && (
|
{search && (
|
||||||
<button
|
<button
|
||||||
onClick={() => { setSearch(''); setDebouncedSearch(''); }}
|
onClick={() => setSearch('')}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<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