diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 086174e41..5f7469050 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -11,7 +11,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { DateTooltip } from "@/components/shared/date-tooltip"; @@ -66,16 +66,12 @@ export const ShowProjects = () => { const { data, isLoading } = api.project.all.useQuery(); const { data: auth } = api.user.get.useQuery(); const { mutateAsync } = api.project.remove.useMutation(); - const [searchQuery, setSearchQuery] = useState(() => { - // Initialize from URL query parameter on mount - if (typeof window !== "undefined") { - const urlQuery = router.query.q; - return typeof urlQuery === "string" ? urlQuery : ""; - } - return ""; - }); + + const [searchQuery, setSearchQuery] = useState( + router.isReady && typeof router.query.q === "string" ? router.query.q : "", + ); const debouncedSearchQuery = useDebounce(searchQuery, 500); - const isUrlSync = useRef(false); + const [sortBy, setSortBy] = useState(() => { if (typeof window !== "undefined") { return localStorage.getItem("projectsSort") || "createdAt-desc"; @@ -87,63 +83,33 @@ export const ShowProjects = () => { localStorage.setItem("projectsSort", 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(() => { if (!router.isReady) return; - - const urlQuery = router.query.q; - const urlSearchQuery = typeof urlQuery === "string" ? urlQuery : ""; - - // Update local state if URL changed externally (e.g., browser navigation) - if (urlSearchQuery !== searchQuery) { - isUrlSync.current = true; - setSearchQuery(urlSearchQuery); + const urlQuery = typeof router.query.q === "string" ? router.query.q : ""; + if (urlQuery !== searchQuery) { + setSearchQuery(urlQuery); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady, router.query.q]); - // Sync search query to URL when debounced value changes useEffect(() => { 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) - if (isUrlSync.current) { - isUrlSync.current = false; - return; + const newQuery = { ...router.query }; + if (debouncedSearchQuery) { + newQuery.q = debouncedSearchQuery; + } else { + delete newQuery.q; } - - const currentQuery = router.query.q; - const normalizedCurrentQuery = - typeof currentQuery === "string" ? currentQuery : ""; - - // 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]); + router.replace({ pathname: router.pathname, query: newQuery }, undefined, { + shallow: true, + }); + }, [debouncedSearchQuery]); const filteredProjects = useMemo(() => { if (!data) return []; - // First filter by search query const filtered = data.filter( (project) => project.name