refactor(dashboard): streamline search query handling and URL synchronization in project display

This commit is contained in:
Mauricio Siu
2025-12-07 20:21:02 -06:00
parent c267faef08
commit 0f6ac310b5
@@ -11,7 +11,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
import { DateTooltip } from "@/components/shared/date-tooltip"; import { DateTooltip } from "@/components/shared/date-tooltip";
@@ -66,16 +66,12 @@ export const ShowProjects = () => {
const { data, isLoading } = api.project.all.useQuery(); const { data, isLoading } = api.project.all.useQuery();
const { data: auth } = api.user.get.useQuery(); const { data: auth } = api.user.get.useQuery();
const { mutateAsync } = api.project.remove.useMutation(); const { mutateAsync } = api.project.remove.useMutation();
const [searchQuery, setSearchQuery] = useState(() => {
// Initialize from URL query parameter on mount const [searchQuery, setSearchQuery] = useState(
if (typeof window !== "undefined") { router.isReady && typeof router.query.q === "string" ? router.query.q : "",
const urlQuery = router.query.q; );
return typeof urlQuery === "string" ? urlQuery : "";
}
return "";
});
const debouncedSearchQuery = useDebounce(searchQuery, 500); const debouncedSearchQuery = useDebounce(searchQuery, 500);
const isUrlSync = useRef(false);
const [sortBy, setSortBy] = useState<string>(() => { const [sortBy, setSortBy] = useState<string>(() => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
return localStorage.getItem("projectsSort") || "createdAt-desc"; return localStorage.getItem("projectsSort") || "createdAt-desc";
@@ -87,63 +83,33 @@ export const ShowProjects = () => {
localStorage.setItem("projectsSort", sortBy); localStorage.setItem("projectsSort", sortBy);
}, [sortBy]); }, [sortBy]);
// Sync URL changes back to state (e.g., browser back/forward)
// NOTE: This effect MUST appear before the URL sync effect below
// because React executes effects in order. When the back button is pressed,
// we need to set isUrlSync.current = true BEFORE the URL sync effect runs.
useEffect(() => { useEffect(() => {
if (!router.isReady) return; if (!router.isReady) return;
const urlQuery = typeof router.query.q === "string" ? router.query.q : "";
const urlQuery = router.query.q; if (urlQuery !== searchQuery) {
const urlSearchQuery = typeof urlQuery === "string" ? urlQuery : ""; setSearchQuery(urlQuery);
// Update local state if URL changed externally (e.g., browser navigation)
if (urlSearchQuery !== searchQuery) {
isUrlSync.current = true;
setSearchQuery(urlSearchQuery);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady, router.query.q]); }, [router.isReady, router.query.q]);
// Sync search query to URL when debounced value changes
useEffect(() => { useEffect(() => {
if (!router.isReady) return; if (!router.isReady) return;
const urlQuery = typeof router.query.q === "string" ? router.query.q : "";
if (debouncedSearchQuery === urlQuery) return;
// Skip if this change came from URL navigation (browser back/forward) const newQuery = { ...router.query };
if (isUrlSync.current) { if (debouncedSearchQuery) {
isUrlSync.current = false; newQuery.q = debouncedSearchQuery;
return; } else {
delete newQuery.q;
} }
router.replace({ pathname: router.pathname, query: newQuery }, undefined, {
const currentQuery = router.query.q; shallow: true,
const normalizedCurrentQuery = });
typeof currentQuery === "string" ? currentQuery : ""; }, [debouncedSearchQuery]);
// Only update URL if the debounced value is different from current URL
if (debouncedSearchQuery !== normalizedCurrentQuery) {
const newQuery = { ...router.query };
if (debouncedSearchQuery) {
newQuery.q = debouncedSearchQuery;
} else {
delete newQuery.q;
}
router.push(
{
pathname: router.pathname,
query: newQuery,
},
undefined,
{ shallow: false },
);
}
}, [debouncedSearchQuery, router]);
const filteredProjects = useMemo(() => { const filteredProjects = useMemo(() => {
if (!data) return []; if (!data) return [];
// First filter by search query
const filtered = data.filter( const filtered = data.filter(
(project) => (project) =>
project.name project.name