11"use client" ;
22
3- import { useEffect , useState , useCallback } from "react" ;
3+ import { useEffect , useRef , useState , useCallback } from "react" ;
44import { motion } from "framer-motion" ;
55import { useRouter } from "next/navigation" ;
66import Link from "next/link" ;
@@ -9,7 +9,7 @@ import XPProgressBar from "@/components/XPProgressBar";
99import TopicCard from "@/components/TopicCard" ;
1010import RecommendedCard from "@/components/RecommendedCard" ;
1111import LoadingScreen from "@/components/LoadingScreen" ;
12- import { useToast } from "@/components/Toast" ;
12+ import { useToast , ToastContainer } from "@/components/Toast" ;
1313import { useRealtimeXP } from "@/lib/useRealtimeXP" ;
1414import { useAuth } from "@/lib/useAuth" ;
1515import { useLoadingState } from "@/hooks/useLoadingState" ;
@@ -61,6 +61,7 @@ export default function Dashboard() {
6161 const { userId, user, loading : authLoading } = useAuth ( ) ;
6262 const router = useRouter ( ) ;
6363 const [ progress , setProgress ] = useState < UserProgress | null > ( null ) ;
64+ const prevLevelRef = useRef < number > ( - 1 ) ;
6465 const [ recommendations , setRecommendations ] =
6566 useState < RecommendationResponse | null > ( null ) ;
6667 const { isLoading, withLoading } = useLoadingState ( true ) ;
@@ -90,7 +91,7 @@ export default function Dashboard() {
9091 } ) ;
9192
9293 // Real-time updates
93- const { showToast, ToastContainer } = useToast ( ) ;
94+ const { showToast, toasts , dismissToast } = useToast ( ) ;
9495
9596 // Stable callbacks for real-time events
9697 const handleXPGain = useCallback (
@@ -103,17 +104,11 @@ export default function Dashboard() {
103104 } ) ;
104105 showToast ( `${ topic || "Study" } completed!` , xp , "xp" ) ;
105106
106- // Update progress with new XP
107+ // Update progress with new XP — no side effects inside the updater
107108 setProgress ( ( prev ) => {
108109 if ( ! prev ) return prev ;
109110 const newTotalXP = prev . total_xp + xp ;
110111 const newLevel = Math . floor ( newTotalXP / 500 ) + 1 ;
111-
112- // Check for level up
113- if ( newLevel > prev . level ) {
114- handleLevelUp ( newLevel ) ;
115- }
116-
117112 return {
118113 ...prev ,
119114 total_xp : newTotalXP ,
@@ -190,6 +185,20 @@ export default function Dashboard() {
190185 [ ] ,
191186 ) ;
192187
188+ // Detect level-up after state update — side effect kept outside the setState updater
189+ useEffect ( ( ) => {
190+ if ( ! progress ) return ;
191+ if ( prevLevelRef . current === - 1 ) {
192+ // First load: record baseline level without triggering level-up
193+ prevLevelRef . current = progress . level ;
194+ return ;
195+ }
196+ if ( progress . level > prevLevelRef . current ) {
197+ handleLevelUp ( progress . level ) ;
198+ }
199+ prevLevelRef . current = progress . level ;
200+ } , [ progress ?. level ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
201+
193202 // Subscribe to real-time XP updates
194203 const { isConnected } = useRealtimeXP ( {
195204 userId : userId || "" ,
@@ -571,7 +580,7 @@ export default function Dashboard() {
571580 </ div >
572581
573582 { /* Toast Notifications */ }
574- < ToastContainer />
583+ < ToastContainer toasts = { toasts } onDismiss = { dismissToast } />
575584
576585 { /* Celebration Modal */ }
577586 < CelebrationModal
0 commit comments