diff --git a/components/ChangelogTabs.tsx b/components/ChangelogTabs.tsx index a92d8f5..87ee996 100644 --- a/components/ChangelogTabs.tsx +++ b/components/ChangelogTabs.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { PortableTextRenderer } from '@/components/PortableTextRenderer' import { RoadmapItem } from '@/components/RoadmapItem' import type { ChangelogEntry, RoadmapItemData, Tab, VoteState } from '@/types/changelog' @@ -48,15 +48,21 @@ export function ChangelogTabs({ const { user } = useAuth() const [showSignInPrompt, setShowSignInPrompt] = useState(false) + const signInTimerRef = useRef | null>(null) const [votesFetched, setVotesFetched] = useState(false) + // Clear sign-in prompt timer on unmount + useEffect(() => () => { if (signInTimerRef.current) clearTimeout(signInTimerRef.current) }, []) + + // Stable key for roadmapItems identity — avoids unstable array reference in deps + const roadmapIdKey = roadmapItems.map((i) => i._id).join(',') + // Fetch vote state when the What's Next tab is first viewed useEffect(() => { - if (activeTab !== 'whats-next' || votesFetched || roadmapItems.length === 0) return + if (activeTab !== 'whats-next' || votesFetched || !roadmapIdKey) return setVotesFetched(true) - const ids = roadmapItems.map((i) => i._id).join(',') - fetch(`/api/roadmap/votes?ids=${encodeURIComponent(ids)}`, { + fetch(`/api/roadmap/votes?ids=${encodeURIComponent(roadmapIdKey)}`, { credentials: 'include', }) .then((r) => (r.ok ? r.json() : {})) @@ -72,12 +78,13 @@ export function ChangelogTabs({ .catch(() => { // Spring Boot not yet available — silently keep count=0 }) - }, [activeTab, votesFetched, roadmapItems]) + }, [activeTab, votesFetched, roadmapIdKey]) // eslint-disable-line react-hooks/exhaustive-deps async function handleVote(id: string) { if (!user) { + if (signInTimerRef.current) clearTimeout(signInTimerRef.current) setShowSignInPrompt(true) - setTimeout(() => setShowSignInPrompt(false), 4000) + signInTimerRef.current = setTimeout(() => setShowSignInPrompt(false), 4000) return }