mirror of
https://github.com/ChiChou/entdb.git
synced 2026-06-10 23:07:47 +02:00
Sync text filter keyword to URL query param
Extract a useQueryFilter hook that debounces the filter keyword and mirrors it into the URL's q param via router.replace, so deep links open pre-filtered and rapid typing doesn't flood history. Apply across keys, files, find, and oslist, replacing their ad-hoc debounce logic.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { Search, X, ChevronsUpDown, ChevronsDownUp } from "lucide-react";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -10,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||
import FileSystem from "@/components/filesystem";
|
||||
import { HeaderPortal } from "@/components/header-portal";
|
||||
import { createEngine } from "@/lib/engine";
|
||||
import { useQueryFilter } from "@/hooks/use-query-filter";
|
||||
|
||||
export default function Files() {
|
||||
const params = useSearchParams();
|
||||
@@ -18,10 +18,9 @@ export default function Files() {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [files, setFiles] = useState<string[]>([]);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [expandAll, setExpandAll] = useState<boolean | null>(null);
|
||||
|
||||
const [debouncedKeyword] = useDebounce(keyword, 200);
|
||||
const { keyword, setKeyword, debouncedKeyword } = useQueryFilter("q", 200);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||
import FileSystem from "@/components/filesystem";
|
||||
import { HeaderPortal } from "@/components/header-portal";
|
||||
import { createEngine } from "@/lib/engine";
|
||||
import { useQueryFilter } from "@/hooks/use-query-filter";
|
||||
|
||||
export default function FindByKey() {
|
||||
const params = useSearchParams();
|
||||
@@ -29,17 +30,8 @@ export default function FindByKey() {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [debouncedKeyword, setDebouncedKeyword] = useState("");
|
||||
const [expandAll, setExpandAll] = useState<boolean | null>(null);
|
||||
|
||||
// Debounce keyword with 300ms delay
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedKeyword(keyword);
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [keyword]);
|
||||
const { keyword, setKeyword, debouncedKeyword } = useQueryFilter();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPaths() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { HeaderPortal } from "@/components/header-portal";
|
||||
import { createEngine } from "@/lib/engine";
|
||||
import { tokenizeKeys, getTopTokens } from "@/lib/tokenizer";
|
||||
import { useQueryFilter } from "@/hooks/use-query-filter";
|
||||
|
||||
export default function Keys() {
|
||||
const params = useSearchParams();
|
||||
@@ -18,15 +19,7 @@ export default function Keys() {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [keys, setKeys] = useState<string[]>([]);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [debouncedKeyword, setDebouncedKeyword] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedKeyword(keyword);
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [keyword]);
|
||||
const { keyword, setKeyword, debouncedKeyword } = useQueryFilter();
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { useQueryFilter } from "@/hooks/use-query-filter";
|
||||
import { dataURL } from "@/lib/env";
|
||||
import type { Group, OS } from "@/lib/types";
|
||||
import { HeaderPortal } from "./header-portal";
|
||||
@@ -74,15 +75,7 @@ export default function OSList() {
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [highlights, setHighlights] = useState<Set<string>>(new Set());
|
||||
const [selectedPlatform, setSelectedPlatform] = useState<string | null>(null);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [debouncedKeyword, setDebouncedKeyword] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedKeyword(keyword);
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [keyword]);
|
||||
const { keyword, setKeyword, debouncedKeyword } = useQueryFilter("q", 200);
|
||||
|
||||
useEffect(() => {
|
||||
const set: Set<string> = new Set();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import { useDebounce } from "use-debounce";
|
||||
|
||||
import { basePath } from "@/lib/env";
|
||||
|
||||
/**
|
||||
* Debounced text filter that mirrors its value into a URL query param.
|
||||
*
|
||||
* - Initializes the keyword from the param so deep links open pre-filtered.
|
||||
* - Debounces, so rapid typing ("c" → "co" → "com") only writes the URL once.
|
||||
* - Uses router.replace (not push), so intermediate filter states don't
|
||||
* pollute browser history.
|
||||
*/
|
||||
export function useQueryFilter(param = "q", delay = 300) {
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const [keyword, setKeyword] = useState(() => params.get(param) ?? "");
|
||||
const [debouncedKeyword] = useDebounce(keyword, delay);
|
||||
|
||||
useEffect(() => {
|
||||
const next = new URLSearchParams(params.toString());
|
||||
if (debouncedKeyword) {
|
||||
next.set(param, debouncedKeyword);
|
||||
} else {
|
||||
next.delete(param);
|
||||
}
|
||||
|
||||
// usePathname() may include the configured basePath, which router.replace
|
||||
// re-applies — strip it to avoid doubling (mirrors version-switcher).
|
||||
const path =
|
||||
basePath && pathname.startsWith(basePath)
|
||||
? pathname.slice(basePath.length)
|
||||
: pathname;
|
||||
const query = next.toString();
|
||||
router.replace(query ? `${path}?${query}` : path, { scroll: false });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedKeyword]);
|
||||
|
||||
return { keyword, setKeyword, debouncedKeyword };
|
||||
}
|
||||
Reference in New Issue
Block a user