feat: implement SkillContext for optimized data fetching
This commit is contained in:
76
web-app/src/context/SkillContext.tsx
Normal file
76
web-app/src/context/SkillContext.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import type { Skill, StarMap } from '../types';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
interface SkillContextType {
|
||||
skills: Skill[];
|
||||
stars: StarMap;
|
||||
loading: boolean;
|
||||
refreshSkills: () => Promise<void>;
|
||||
}
|
||||
|
||||
const SkillContext = createContext<SkillContextType | undefined>(undefined);
|
||||
|
||||
export function SkillProvider({ children }: { children: React.ReactNode }) {
|
||||
const [skills, setSkills] = useState<Skill[]>([]);
|
||||
const [stars, setStars] = useState<StarMap>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchSkillsAndStars = useCallback(async (silent = false) => {
|
||||
if (!silent) setLoading(true);
|
||||
try {
|
||||
// Fetch skills index
|
||||
const res = await fetch('/skills.json');
|
||||
const data = await res.json();
|
||||
setSkills(data);
|
||||
|
||||
// Fetch stars from Supabase if available
|
||||
if (supabase) {
|
||||
const { data: starData, error } = await supabase
|
||||
.from('skill_stars')
|
||||
.select('skill_id, star_count');
|
||||
|
||||
if (!error && starData) {
|
||||
const starMap: StarMap = {};
|
||||
starData.forEach((item: { skill_id: string; star_count: number }) => {
|
||||
starMap[item.skill_id] = item.star_count;
|
||||
});
|
||||
setStars(starMap);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('SkillContext: Failed to load skills', err);
|
||||
} finally {
|
||||
if (!silent) setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSkillsAndStars();
|
||||
}, [fetchSkillsAndStars]);
|
||||
|
||||
const refreshSkills = useCallback(async () => {
|
||||
await fetchSkillsAndStars(true);
|
||||
}, [fetchSkillsAndStars]);
|
||||
|
||||
const value = useMemo(() => ({
|
||||
skills,
|
||||
stars,
|
||||
loading,
|
||||
refreshSkills
|
||||
}), [skills, stars, loading, refreshSkills]);
|
||||
|
||||
return (
|
||||
<SkillContext.Provider value={value}>
|
||||
{children}
|
||||
</SkillContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSkills() {
|
||||
const context = useContext(SkillContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useSkills must be used within a SkillProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { SkillProvider } from './context/SkillContext';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (!rootElement) {
|
||||
@@ -10,6 +11,8 @@ if (!rootElement) {
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<SkillProvider>
|
||||
<App />
|
||||
</SkillProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user