Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions src/hooks/useLearningProgress.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use client";
'use client';

import { useState, useEffect } from 'react';
import { useAuth } from '@/context/AuthContext';
Expand All @@ -19,12 +19,9 @@ export function useLearningProgress(): UseLearningProgressResult {

useEffect(() => {
if (!user) {
setCompletedNodes([]);
setLoading(false);
return;
}

setLoading(true);
const docRef = doc(db, 'user_progress', user.uid);
const unsubscribe = onSnapshot(
docRef,
Expand All @@ -38,28 +35,31 @@ export function useLearningProgress(): UseLearningProgressResult {
setLoading(false);
},
(error) => {
console.error("Error listening to learning progress:", error);
console.error('Error listening to learning progress:', error);
setLoading(false);
}
);

return () => unsubscribe();
}, [user]);

// Derive state based on user authentication status to avoid cascading renders
const actualCompletedNodes = user ? completedNodes : [];
const actualLoading = user ? loading : false;

const toggleNode = async (pathId: string, nodeId: string) => {
if (!user) return;

const nodeKey = `${pathId}-${nodeId}`;
const isCompleted = completedNodes.includes(nodeKey);
const isCompleted = actualCompletedNodes.includes(nodeKey);

// Optimistic UI Update: Reflect state immediately in local array
const previousCompletedNodes = [...completedNodes];
const nextCompletedNodes = isCompleted
? completedNodes.filter((id) => id !== nodeKey)
: [...completedNodes, nodeKey];

setCompletedNodes(nextCompletedNodes);
? actualCompletedNodes.filter((id) => id !== nodeKey)
: [...actualCompletedNodes, nodeKey];

// Rely exclusively on Firestore's native latency compensation (which instantly triggers
// the onSnapshot listener locally) instead of manually maintaining optimistic state
// which leads to race conditions.
try {
const docRef = doc(db, 'user_progress', user.uid);
await setDoc(
Expand All @@ -72,19 +72,17 @@ export function useLearningProgress(): UseLearningProgressResult {
{ merge: true }
);
} catch (error) {
console.error("Failed to save learning progress:", error);
// Revert state on error if background write fails
setCompletedNodes(previousCompletedNodes);
console.error('Failed to save learning progress:', error);
}
};

const isNodeCompleted = (pathId: string, nodeId: string) => {
return completedNodes.includes(`${pathId}-${nodeId}`);
return actualCompletedNodes.includes(`${pathId}-${nodeId}`);
};

return {
completedNodes,
loading,
completedNodes: actualCompletedNodes,
loading: actualLoading,
toggleNode,
isNodeCompleted,
};
Expand Down
Loading