From b1798bde3a553d4255f9bdf8e370e598431760dc Mon Sep 17 00:00:00 2001 From: Sunday Abel Date: Mon, 1 Jun 2026 08:33:38 +0000 Subject: [PATCH] fix: prevent rapid double-tap from pushing duplicate history entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The useTransition guard alone was insufficient because isPending is an async state update — a second tap could arrive before React re-renders. A ref-based lock (set synchronously on click, cleared only when pathname actually changes via useEffect) ensures no second navigation can start while one is in-flight. --- components/mobile-nav.tsx | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/components/mobile-nav.tsx b/components/mobile-nav.tsx index 6fbdefb..811184f 100644 --- a/components/mobile-nav.tsx +++ b/components/mobile-nav.tsx @@ -1,8 +1,7 @@ "use client"; -import React, { useState, useEffect } from "react"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; +import React, { useRef, useState, useEffect, useTransition } from "react"; +import { usePathname, useRouter } from "next/navigation"; import { Home, Send, Coins, Briefcase, User, Wallet } from "lucide-react"; interface NavItem { @@ -26,8 +25,15 @@ const navItems: NavItem[] = [ export function MobileNav() { const pathname = usePathname(); + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const navigatingTo = useRef(null); const [bottomOffset, setBottomOffset] = useState(0); + useEffect(() => { + navigatingTo.current = null; + }, [pathname]); + useEffect(() => { if (typeof window === "undefined" || !window.visualViewport) return; @@ -50,6 +56,14 @@ export function MobileNav() { }; }, []); + function handleNav(href: string) { + if (isPending || navigatingTo.current !== null || pathname === href) return; + navigatingTo.current = href; + startTransition(() => { + router.push(href); + }); + } + return (