Skip to main content

Audio Hooks

useMediaSession

Integrates with the browser’s Media Session API for system-level media controls. Location: src/hooks/use-media-session.ts:19
audioRef
React.RefObject<HTMLAudioElement | null>
required
Reference to the audio element
playlist
Playlist
required
Current playlist array
currentTrackId
number
required
Index of currently playing track
isPlaying
boolean
required
Current playback state
onPlay
() => void
required
Callback when play action triggered
onPause
() => void
required
Callback when pause action triggered
onNext
() => void
required
Callback when next track action triggered
onPrev
() => void
required
Callback when previous track action triggered
Features:
  • Sets media metadata (title, artist, artwork)
  • Handles system media controls (play, pause, next, previous)
  • Updates position state every second
  • Automatically cleans up on unmount
import { useMediaSession } from '@/hooks/use-media-session';
import { useRef } from 'react';

const audioRef = useRef<HTMLAudioElement>(null);

useMediaSession({
  audioRef,
  playlist,
  currentTrackId: 0,
  isPlaying: true,
  onPlay: () => audioRef.current?.play(),
  onPause: () => audioRef.current?.pause(),
  onNext: handleNextTrack,
  onPrev: handlePreviousTrack,
});

useSleepTimer

Manages a sleep timer that pauses playback after a specified duration. Location: src/hooks/use-sleep-timer.ts:3
togglePlayPause
() => void
required
Function to pause playback when timer expires
remainingTime
number | null
Remaining time in seconds, or null if no timer is active
setSleepTimer
(minutes: number | 'end') => void
Start a sleep timer. Pass number for minutes, or 'end' for 120 minutes
clearSleepTimer
() => void
Cancel the active sleep timer
Features:
  • Real-time countdown with 1-second updates
  • Automatic cleanup on unmount
  • Supports both fixed durations and “until end” mode
  • Pauses playback when timer expires
import { useSleepTimer } from '@/hooks/use-sleep-timer';

const { remainingTime, setSleepTimer, clearSleepTimer } = 
  useSleepTimer(() => audioRef.current?.pause());

// Set 15 minute timer
<button onClick={() => setSleepTimer(15)}>
  Sleep 15m
</button>

// Set "until end" timer (120 minutes)
<button onClick={() => setSleepTimer('end')}>
  Until End
</button>

// Display remaining time
{remainingTime && (
  <span>{Math.ceil(remainingTime / 60)}m remaining</span>
)}

// Cancel timer
<button onClick={clearSleepTimer}>Cancel</button>

State Management Hooks

useFavorites

Manages favorite reciters with local storage and real-time sync. Location: src/hooks/use-favorites.ts:11 Parameters: None
favoriteReciters
string[]
Array of favorite reciter IDs
favoriteCounts
Record<string, number>
Global favorite counts for all reciters (from GunDB)
toggleFavorite
(favId: string) => void
Add or remove a reciter from favorites
showOnlyFavorites
boolean
Filter state for showing only favorites
setShowOnlyFavorites
(show: boolean) => void
Toggle favorites-only filter
Features:
  • Syncs with GunDB for global favorite counts
  • Real-time updates via subscription
  • Persists to local storage via Jotai atom
import { useFavorites } from '@/hooks/use-favorites';
import { generateFavId } from '@/utils';

const { favoriteReciters, favoriteCounts, toggleFavorite } = useFavorites();

const reciter = { id: 1, moshaf: { id: '5' } };
const favId = generateFavId(reciter);
const isFavorite = favoriteReciters.includes(favId);
const globalCount = favoriteCounts[favId] || 0;

<button onClick={() => toggleFavorite(favId)}>
  {isFavorite ? 'Unfavorite' : 'Favorite'}
  ({globalCount} favorites)
</button>

useFilterSort

Provides filtering and sorting logic for reciter lists. Location: src/hooks/use-filter-sort.ts:19
reciters
Reciter[]
required
Array of all reciters to filter/sort
favoriteReciters
string[]
required
Array of favorite reciter IDs
showOnlyFavorites
boolean
required
Whether to show only favorites
favoriteCounts
Record<string, number>
default:"{}"
Global favorite counts for sorting
viewCounts
Record<string, number>
default:"{}"
View counts for sorting
searchTerm
string
Current search query
setSearchTerm
(term: string) => void
Update search query
selectedRiwaya
Riwaya | 'all'
Currently selected riwaya filter
setSelectedRiwaya
(riwaya: Riwaya | 'all') => void
Change riwaya filter
sortMode
'popular' | 'alphabetical' | 'views'
Current sort mode
setSortMode
(mode: string) => void
Change sort mode
filteredReciters
Reciter[]
Filtered and sorted reciter array
availableRiwiyat
{ value: Riwaya, label: string }[]
Available riwaya options with localized labels
Features:
  • Search by normalized Arabic text
  • Filter by riwaya type
  • Filter by favorites
  • Sort by popularity, alphabetical, or views
  • Automatically updates when inputs change
  • Internationalized riwaya labels
import { useFilterSort } from '@/hooks/use-filter-sort';
import { useFavorites } from '@/hooks/use-favorites';

const { favoriteReciters, showOnlyFavorites, favoriteCounts } = useFavorites();
const {
  searchTerm,
  setSearchTerm,
  filteredReciters,
  selectedRiwaya,
  setSelectedRiwaya,
  sortMode,
  setSortMode,
  availableRiwiyat
} = useFilterSort({
  reciters: allReciters,
  favoriteReciters,
  showOnlyFavorites,
  favoriteCounts,
});

return (
  <>
    <input 
      value={searchTerm} 
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search reciters..."
    />
    
    <select value={sortMode} onChange={(e) => setSortMode(e.target.value)}>
      <option value="popular">Popular</option>
      <option value="alphabetical">A-Z</option>
      <option value="views">Most Viewed</option>
    </select>

    {filteredReciters.map(reciter => (
      <div key={reciter.id}>{reciter.name}</div>
    ))}
  </>
);

UI Hooks

useKeyboardNavigation

Enables keyboard navigation through a list with arrow keys. Location: src/hooks/use-keyboard-navigation.ts:3
itemCount
number
required
Total number of navigable items
focusedIndex
number | null
Currently focused item index
setFocusedIndex
(index: number | null) => void
Manually set focused index
reciterRefs
RefObject<(HTMLElement | null)[]>
Array ref for storing element references
searchInputRef
RefObject<HTMLInputElement | null>
Ref for search input (focused on ESC)
Features:
  • Arrow Up/Down navigation with wraparound
  • ESC to unfocus and return to search
  • Auto-scroll focused item into view
  • Smooth scrolling behavior
import { useKeyboardNavigation } from '@/hooks/use-keyboard-navigation';

const { 
  focusedIndex, 
  setFocusedIndex, 
  reciterRefs, 
  searchInputRef 
} = useKeyboardNavigation(reciters.length);

return (
  <>
    <input ref={searchInputRef} placeholder="Search..." />
    
    {reciters.map((reciter, index) => (
      <button
        key={reciter.id}
        ref={(el) => { reciterRefs.current[index] = el; }}
        onClick={() => setFocusedIndex(index)}
        className={focusedIndex === index ? 'focused' : ''}
      >
        {reciter.name}
      </button>
    ))}
  </>
);

useDirection

Determines text direction (LTR/RTL) based on current locale. Location: src/hooks/use-direction.ts:3 Parameters: None
isRTL
boolean
true if current locale is Arabic (RTL), false otherwise
Features:
  • Uses react-intl locale context
  • Simple boolean output for conditional rendering
import useDirection from '@/hooks/use-direction';

const { isRTL } = useDirection();

return (
  <div dir={isRTL ? 'rtl' : 'ltr'}>
    <input 
      style={{ 
        textAlign: isRTL ? 'right' : 'left',
        backgroundPosition: isRTL ? 'right' : 'left'
      }} 
    />
  </div>
);

Data Fetching Hooks

useReciters

Fetches and manages the list of available Quran reciters from the MP3 Quran API. Location: src/hooks/use-reciters.ts:11 Parameters: None (uses locale from react-intl context)
reciters
Reciter[]
Array of available reciters with their moshaf details
loading
boolean
true while fetching data, false when complete
error
string | null
Error message if fetch failed, null otherwise
Features:
  • Automatically fetches based on current locale (Arabic/English)
  • Manages loading and error states
  • Updates selected reciter if it changes
  • Cleans up on unmount to prevent memory leaks
import { useReciters } from '@/hooks/use-reciters';

export function RecitersList() {
  const { reciters, loading, error } = useReciters();

  if (loading) return <div>Loading reciters...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {reciters.map(reciter => (
        <div key={reciter.id}>{reciter.name}</div>
      ))}
    </div>
  );
}

UI Hooks

useEscapeKey

Listens for the Escape key and triggers a callback when pressed. Location: src/hooks/use-escape-key-hook.ts:6
handleClose
() => void
required
Callback function to execute when Escape key is pressed
Features:
  • Uses useLayoutEffect for immediate DOM updates
  • Automatically cleans up event listener on unmount
  • Commonly used for closing modals and dialogs
import useEscapeKey from '@/hooks/use-escape-key-hook';

export function Modal({ onClose, children }) {
  useEscapeKey(onClose);

  return (
    <div className="modal">
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Build docs developers (and LLMs) love