React Hooks ve Custom Hooks
Paylaşılan React hook'ları: useLang, useVideoTelemetry, useChatNotifications ve kullanım örnekleri.
Achidemy projesinde paylaşılan React hook’ları app/hooks/ klasöründe bulunur. Bu hook’lar route ve component’lerde tekrar kullanılır ve tutarlı davranış sağlar.
useLang
Section titled “useLang”Dosya: app/hooks/useLang.ts
Amaç: Mevcut dili ve dil destekli URL oluşturma fonksiyonlarını sağlar.
Kullanım
Section titled “Kullanım”import { useLang } from '~/hooks/useLang';
function MyComponent() { const { lang, getLocalizedPath, getPath } = useLang();
// Mevcut dil: 'tr', 'en', 'de', 'es', vb. console.log(lang); // 'tr'
// Dil prefix'li path oluştur const localizedPath = getLocalizedPath('/courses'); // '/tr/courses'
// Admin/API route'ları için path'i olduğu gibi döndür const adminPath = getPath('/admin/users'); // '/admin/users'}Dönen Değerler
Section titled “Dönen Değerler”| Değer | Tip | Açıklama |
|---|---|---|
lang | SupportedLanguage | Mevcut dil kodu ('tr', 'en', 'de', 'es', 'fr', 'ja') |
getLocalizedPath | (path: string) => string | Path’e mevcut dil kodunu ekler veya değiştirir |
getPath | (path: string) => string | Admin/API route’ları için path’i olduğu gibi döndürür, diğerleri için dil ekler |
Örnekler
Section titled “Örnekler”// Dil değiştirme linkiconst { lang, getLocalizedPath } = useLang();const englishPath = getLocalizedPath('/courses'); // Mevcut dil 'tr' ise '/en/courses'
// Navigasyon<Link to={getLocalizedPath('/instructor/dashboard')}> Dashboard</Link>useVideoTelemetry
Section titled “useVideoTelemetry”Dosya: app/hooks/useVideoTelemetry.ts
Amaç: Video izlenme süresini kayıpsız tampon (lossless buffer) ile takip eder ve backend’e 15 saniyelik heartbeat blokları halinde gönderir. Sunucu 200 OK dönmeden client tamponundan düşmez.
Kullanım
Section titled “Kullanım”import { useVideoTelemetry } from "~/hooks/useVideoTelemetry";
export function CoursePlayerTelemetry({ courseId, lessonId, isPlaying }: { courseId?: string; lessonId?: string; isPlaying: boolean }) { useVideoTelemetry({ courseId, lessonId, isPlaying }); return null;}Parametreler
Section titled “Parametreler”| Parametre | Tip | Açıklama |
|---|---|---|
courseId | string | undefined | Kurs ID |
lessonId | string | undefined | Ders ID |
isPlaying | boolean | Video oynuyor mu? (learn.$slug içinde “video time ilerliyor mu?” ile türetilir) |
Backend Entegrasyonu
Section titled “Backend Entegrasyonu”- Endpoint:
POST /api/video-telemetry - Blok boyutu: 15 saniye (heartbeat)
- Anti-cheat: tek istekte
durationSeconds <= 25
useLearningTracker (Legacy / Deprecated)
Section titled “useLearningTracker (Legacy / Deprecated)”Dosya: app/hooks/useLearningTracker.ts
Amaç: (Legacy) Dakika bazlı izlenme raporu ve /api/update-progress üzerinden heartbeat. Yeni sistemde useVideoTelemetry kullanılmalıdır.
Kullanım
Section titled “Kullanım”import { useLearningTracker } from '~/hooks/useLearningTracker';
function VideoPlayer({ video, courseId, lessonId, instructorId, isSubscriber }) { const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0);
const { totalMinutes, totalSeconds } = useLearningTracker({ isPlaying, courseId, lessonId, instructorId, isSubscriber, currentTimeSeconds: currentTime, onMinuteCompleted: (minutes) => { console.log(`${minutes} dakika izlendi`); } });
return ( <div> <video onPlay={() => setIsPlaying(true)} onPause={() => setIsPlaying(false)} /> <p>Toplam izlenen: {totalMinutes} dakika</p> </div> );}Parametreler
Section titled “Parametreler”| Parametre | Tip | Açıklama |
|---|---|---|
isPlaying | boolean | Video oynatılıyor mu? |
courseId | string | Kurs ID |
lessonId | string | Ders ID |
instructorId | string | Eğitmen ID |
isSubscriber | boolean | Abonelik ile mi izleniyor? |
currentTimeSeconds | number | Videonun o anki konumu (saniye) |
onMinuteCompleted | (totalMinutes: number) => void | Her dakika tamamlandığında çağrılır (opsiyonel) |
Dönen Değerler
Section titled “Dönen Değerler”| Değer | Tip | Açıklama |
|---|---|---|
totalMinutes | number | Toplam izlenen dakika (tam sayı) |
totalSeconds | number | Toplam izlenen saniye |
Çalışma Mantığı
Section titled “Çalışma Mantığı”- İzleme Takibi: Video oynatıldığında süre biriktirilir
- Dakika Tamamlama: Her dakika tamamlandığında:
- GraphQL mutation:
recordLearningActivity(minutes)çağrılır - Streak ve günlük aktivite kaydı yapılır
- GraphQL mutation:
- Heartbeat: (Legacy) Her ~30 saniyede bir
/api/update-progressendpoint’ine ilerleme gönderilirsecondsWatched,courseId,lessonId,instructorId,isSubscriptionbilgileri gönderilir
Backend Entegrasyonu
Section titled “Backend Entegrasyonu”GraphQL Mutation:
mutation RecordActivity($minutes: Int!) { recordLearningActivity(minutes: $minutes)}API Endpoint (Legacy): /api/update-progress
POST /api/update-progress{ courseId: string; lessonId: string; instructorId: string; secondsWatched: number; isSubscription: boolean;}useChatNotifications
Section titled “useChatNotifications”Dosya: app/hooks/useChatNotifications.ts
Amaç: Kullanıcının okunmamış mesaj sayısını takip eder ve WebSocket ile gerçek zamanlı güncelleme sağlar.
Kullanım
Section titled “Kullanım”import { useChatNotifications } from '~/hooks/useChatNotifications';
function Navbar() { const { user } = useAuth(); const { unreadCount, refetch } = useChatNotifications(user?.id);
return ( <nav> <Link to="/messages"> Mesajlar {unreadCount > 0 && ( <Badge>{unreadCount}</Badge> )} </Link> </nav> );}Parametreler
Section titled “Parametreler”| Parametre | Tip | Açıklama |
|---|---|---|
userId | string | undefined | Kullanıcı ID (undefined ise hook çalışmaz) |
Dönen Değerler
Section titled “Dönen Değerler”| Değer | Tip | Açıklama |
|---|---|---|
unreadCount | number | Okunmamış mesaj sayısı |
refetch | () => Promise<void> | Manuel olarak sayıyı yeniden çekme fonksiyonu |
Çalışma Mantığı
Section titled “Çalışma Mantığı”- İlk Yükleme: Component mount olduğunda okunmamış mesaj sayısı çekilir
- WebSocket Bağlantısı:
/api/chat?userId={userId}&convId={convId}üzerinden WebSocket bağlantısı kurulur - Gerçek Zamanlı Güncelleme:
NEW_MESSAGEevent’i geldiğinde sayı artırılırUPDATE_BADGEevent’i geldiğinde sayı güncellenir
- Sayfa Görünürlüğü: Sayfa görünür olduğunda sayı yeniden çekilir
- Periyodik Güncelleme: Her 30 saniyede bir sayı yeniden çekilir (WebSocket bağlantısı yoksa)
WebSocket Event Tipleri
Section titled “WebSocket Event Tipleri”| Event | Açıklama |
|---|---|
NEW_MESSAGE | Yeni mesaj geldiğinde |
UPDATE_BADGE | Badge sayısı güncellendiğinde |
Route sayfalarında hook sırası
Section titled “Route sayfalarında hook sırası”Loader verisi kullanan route bileşenlerinde skeleton veya loading göstermek için erken return kullanılıyorsa, React’in hook kurallarına uyulmalıdır. Aksi halde sayfa geçişlerinde “Rendered fewer hooks than expected” (React #300) hatası oluşur.
- Tüm hook’lar her zaman aynı sırada çağrılmalı. Hiçbir
if (loading) return <Skeleton />veyaif (!data) return nullifadesi,useLoaderData,useState,useEffectvb. hook’lardan önce olmamalı. - Doğru sıra: Önce tüm hook’lar (useNavigation, useLoaderData, useNavigate, useRevalidator, useState’ler, useTranslation, useLang, useEffect vb.), sonra isteğe bağlı hesaplamalar (loader verisi ile, güvenli erişimle), en sonda loading/veri yok return’leri ve asıl JSX.
export default function CourseDetails() { const navigation = useNavigation(); const loaderData = useLoaderData<typeof loader>(); const course = loaderData?.course; const curriculum = loaderData?.curriculum ?? [];
const [previewVideo, setPreviewVideo] = useState(null); const navigate = useNavigate(); const { revalidate } = useRevalidator(); const { t } = useTranslation(); const { getLocalizedPath } = useLang(); useEffect(() => { /* ... */ }, [course?.id]);
// Hesaplamalar (hook'lardan sonra, güvenli erişim) const totalLessons = (curriculum ?? []).reduce( (acc, section) => acc + (section?.lessons ?? []).length, 0 );
if (navigation.state === "loading") return <CoursePageSkeleton />; if (!course) return null;
return (/* asıl JSX */);}Loader verisi loaderData?.field ve ?? [] ile alınır; hesaplamalar hook’lardan sonra yapılır; loading ve !course return’leri en sonda yer alır.
İlgili Dosyalar
Section titled “İlgili Dosyalar”app/hooks/useLang.ts— Dil yönetimi hook’uapp/hooks/useLearningTracker.ts— Öğrenme takibi hook’uapp/hooks/useChatNotifications.ts— Mesaj bildirimleri hook’uapp/lib/i18n-utils.ts— Dil yardımcı fonksiyonlarıapp/lib/streak.ts— Streak yönetimi (useLearningTracker tarafından kullanılır)workers/chat.ts— WebSocket chat (useChatNotifications tarafından kullanılır)