api call optimized
This commit is contained in:
1 parent
97436c6823
commit
612259f020
12 files changed
+727
-177
No files matched your search
@@ -0,0 +1,204 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { useUser, useAuth } from "@clerk/clerk-expo";
|
||||
import {
|
||||
fitnessGoalsService,
|
||||
type FitnessGoal,
|
||||
type CreateGoalData,
|
||||
} from "../services/fitnessGoals";
|
||||
import log from "../utils/logger";
|
||||
|
||||
interface FitnessGoalsContextValue {
|
||||
goals: FitnessGoal[];
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
refetchGoals: () => Promise<void>;
|
||||
createGoal: (goalData: CreateGoalData) => Promise<FitnessGoal>;
|
||||
updateGoal: (
|
||||
id: string,
|
||||
updates: Partial<FitnessGoal>,
|
||||
) => Promise<FitnessGoal>;
|
||||
completeGoal: (id: string) => Promise<FitnessGoal>;
|
||||
deleteGoal: (id: string) => Promise<void>;
|
||||
clearCache: () => void;
|
||||
}
|
||||
|
||||
const FitnessGoalsContext = createContext<FitnessGoalsContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function FitnessGoalsProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { user } = useUser();
|
||||
const { getToken } = useAuth();
|
||||
const [goals, setGoals] = useState<FitnessGoal[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [lastFetchTime, setLastFetchTime] = useState<number>(0);
|
||||
const fetchInProgress = useRef(false);
|
||||
|
||||
// Cache goals for 30 seconds to avoid duplicate calls
|
||||
const CACHE_DURATION = 30000; // 30 seconds
|
||||
|
||||
const refetchGoals = useCallback(async () => {
|
||||
if (!user?.id) return;
|
||||
|
||||
// Prevent concurrent fetches
|
||||
if (fetchInProgress.current) {
|
||||
log.debug("Fetch already in progress, skipping duplicate call");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have recent cached data
|
||||
const now = Date.now();
|
||||
if (goals.length > 0 && now - lastFetchTime < CACHE_DURATION) {
|
||||
log.debug("Using cached fitness goals", {
|
||||
count: goals.length,
|
||||
age: now - lastFetchTime,
|
||||
cacheRemaining: CACHE_DURATION - (now - lastFetchTime),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fetchInProgress.current = true;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
log.debug("Fetching fresh fitness goals", { userId: user.id });
|
||||
|
||||
const token = await getToken();
|
||||
const loadedGoals = await fitnessGoalsService.getGoals(user.id, token);
|
||||
|
||||
setGoals(loadedGoals);
|
||||
setLastFetchTime(now);
|
||||
log.debug("Fitness goals fetched and cached", {
|
||||
count: loadedGoals.length,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
log.error("Failed to fetch fitness goals", error);
|
||||
setError(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
fetchInProgress.current = false;
|
||||
}
|
||||
}, [user?.id, getToken, goals.length, lastFetchTime]);
|
||||
|
||||
const createGoal = useCallback(
|
||||
async (goalData: CreateGoalData): Promise<FitnessGoal> => {
|
||||
if (!user?.id) throw new Error("User not authenticated");
|
||||
|
||||
const token = await getToken();
|
||||
const newGoal = await fitnessGoalsService.createGoal(goalData, token);
|
||||
|
||||
// Optimistically update cache
|
||||
setGoals((prev) => [...prev, newGoal]);
|
||||
setLastFetchTime(Date.now());
|
||||
log.debug("Goal created and added to cache", { goalId: newGoal.id });
|
||||
|
||||
return newGoal;
|
||||
},
|
||||
[user?.id, getToken],
|
||||
);
|
||||
|
||||
const updateGoal = useCallback(
|
||||
async (id: string, updates: Partial<FitnessGoal>): Promise<FitnessGoal> => {
|
||||
if (!user?.id) throw new Error("User not authenticated");
|
||||
|
||||
const token = await getToken();
|
||||
const updatedGoal = await fitnessGoalsService.updateGoal(
|
||||
id,
|
||||
updates,
|
||||
token,
|
||||
);
|
||||
|
||||
// Optimistically update cache
|
||||
setGoals((prev) =>
|
||||
prev.map((goal) => (goal.id === id ? updatedGoal : goal)),
|
||||
);
|
||||
setLastFetchTime(Date.now());
|
||||
log.debug("Goal updated in cache", { goalId: id });
|
||||
|
||||
return updatedGoal;
|
||||
},
|
||||
[user?.id, getToken],
|
||||
);
|
||||
|
||||
const completeGoal = useCallback(
|
||||
async (id: string): Promise<FitnessGoal> => {
|
||||
if (!user?.id) throw new Error("User not authenticated");
|
||||
|
||||
const token = await getToken();
|
||||
const completedGoal = await fitnessGoalsService.completeGoal(id, token);
|
||||
|
||||
// Optimistically update cache
|
||||
setGoals((prev) =>
|
||||
prev.map((goal) => (goal.id === id ? completedGoal : goal)),
|
||||
);
|
||||
setLastFetchTime(Date.now());
|
||||
log.debug("Goal completed in cache", { goalId: id });
|
||||
|
||||
return completedGoal;
|
||||
},
|
||||
[user?.id, getToken],
|
||||
);
|
||||
|
||||
const deleteGoal = useCallback(
|
||||
async (id: string): Promise<void> => {
|
||||
if (!user?.id) throw new Error("User not authenticated");
|
||||
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.deleteGoal(id, token);
|
||||
|
||||
// Optimistically update cache
|
||||
setGoals((prev) => prev.filter((goal) => goal.id !== id));
|
||||
setLastFetchTime(Date.now());
|
||||
log.debug("Goal deleted from cache", { goalId: id });
|
||||
},
|
||||
[user?.id, getToken],
|
||||
);
|
||||
|
||||
const clearCache = useCallback(() => {
|
||||
setGoals([]);
|
||||
setLastFetchTime(0);
|
||||
setError(null);
|
||||
fetchInProgress.current = false;
|
||||
log.debug("Fitness goals cache cleared");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FitnessGoalsContext.Provider
|
||||
value={{
|
||||
goals,
|
||||
loading,
|
||||
error,
|
||||
refetchGoals,
|
||||
createGoal,
|
||||
updateGoal,
|
||||
completeGoal,
|
||||
deleteGoal,
|
||||
clearCache,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FitnessGoalsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useFitnessGoals() {
|
||||
const context = useContext(FitnessGoalsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useFitnessGoals must be used within a FitnessGoalsProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in new issue
Block a user