The useWatchlist hook provides access to the user’s watchlist with automatic synchronization between local storage (for guest users) and Convex backend (for authenticated users).
Overview
This hook is the primary interface for retrieving watchlist data. It automatically handles:
User authentication state detection
Data source switching between local and remote storage
Real-time updates via Convex subscriptions
Optimistic UI updates for immediate feedback
Basic Usage
import { useWatchlist } from '@/hooks/usewatchlist' ;
function WatchlistPage () {
const { watchlist , loading } = useWatchlist ();
if ( loading ) return < div > Loading... </ div > ;
return (
< div >
< h1 > My Watchlist ( { watchlist . length } ) </ h1 >
{ watchlist . map (( item ) => (
< div key = { item . external_id } >
< h2 > { item . title } </ h2 >
< p > Type: { item . type } </ p >
< p > Progress: { item . progress } % </ p >
</ div >
)) }
</ div >
);
}
Return Values
Array of watchlist items, sorted by most recently updated first. Only includes items where inWatchlist is true. Show WatchlistItem properties
Media description/synopsis
Whether item is in watchlist (always true for items returned by useWatchlist)
Current viewing status: 'want-to-watch', 'watching', 'finished', or 'dropped'
User reaction: 'loved', 'liked', 'mixed', or 'not-for-me'
Watch progress percentage (0-100)
Loading state indicator. True when:
User authentication state is not yet loaded
Authenticated user’s data is being fetched from Convex
useWatchlistItem
Check if a specific item is in the watchlist:
import { useWatchlistItem } from '@/hooks/usewatchlist' ;
function MediaCard ({ id , mediaType }) {
const { isOnWatchList } = useWatchlistItem ( id , mediaType );
return (
< div >
{ isOnWatchList ? '✓ In Watchlist' : 'Add to Watchlist' }
</ div >
);
}
Whether the specified media item is currently in the watchlist
useWatchlistCount
Get the total number of items in the watchlist:
import { useWatchlistCount } from '@/hooks/usewatchlist' ;
function WatchlistBadge () {
const count = useWatchlistCount ();
return < span > Watchlist ( { count } ) </ span > ;
}
Get full tracking state for a specific media item:
import { useMediaState } from '@/hooks/usewatchlist' ;
function MediaDetails ({ id , mediaType }) {
const mediaState = useMediaState ( id , mediaType );
if ( ! mediaState ) return < div > Not tracked </ div > ;
return (
< div >
< p > Status: { mediaState . progressStatus } </ p >
< p > Progress: { mediaState . progress } % </ p >
< p > Reaction: { mediaState . reaction } </ p >
</ div >
);
}
Complete state for the media item, or null if not found
useToggleWatchlistItem
Add or remove items from the watchlist:
import { useToggleWatchlistItem } from '@/hooks/usewatchlist' ;
function AddToWatchlistButton ({ movie }) {
const toggleWatchlist = useToggleWatchlistItem ();
const handleClick = async () => {
await toggleWatchlist ({
title: movie . title ,
rating: movie . vote_average ,
image: movie . poster_path ,
id: String ( movie . id ),
media_type: 'movie' ,
release_date: movie . release_date ,
overview: movie . overview ,
});
};
return < button onClick = { handleClick } > Toggle Watchlist </ button > ;
}
useSetProgressStatus
Update the watch progress status for a media item:
import { useSetProgressStatus } from '@/hooks/usewatchlist' ;
function StatusSelector ({ tmdbId , mediaType }) {
const setStatus = useSetProgressStatus ();
return (
< select onChange = { ( e ) => setStatus ({
id: String ( tmdbId ),
mediaType ,
progressStatus: e . target . value
}) } >
< option value = "want-to-watch" > Want to Watch </ option >
< option value = "watching" > Watching </ option >
< option value = "finished" > Finished </ option >
</ select >
);
}
useSetReaction
Set an emotional reaction for a media item:
import { useSetReaction } from '@/hooks/usewatchlist' ;
function ReactionButtons ({ tmdbId , mediaType }) {
const setReaction = useSetReaction ();
return (
< div >
< button onClick = { () => setReaction ({
id: String ( tmdbId ),
mediaType ,
reaction: 'loved'
}) } > ❤️ Loved </ button >
< button onClick = { () => setReaction ({
id: String ( tmdbId ),
mediaType ,
reaction: 'liked'
}) } > 👍 Liked </ button >
</ div >
);
}
useWatchlistItemStatus
Legacy hook that returns the old combined status format for backward compatibility:
import { useWatchlistItemStatus } from '@/hooks/usewatchlist' ;
function LegacyStatusDisplay ({ tmdbId , mediaType }) {
const status = useWatchlistItemStatus ( String ( tmdbId ), mediaType );
// Returns: "plan-to-watch" | "watching" | "completed" | "liked" | null
return < div > Status: { status } </ div > ;
}
This hook maps the new split status model (progressStatus + reaction) back to the legacy
combined status format. New code should use useMediaState instead.
Real-World Example
import { useWatchlist , useMediaState } from '@/hooks/usewatchlist' ;
function WatchlistWithStatus () {
const { watchlist , loading } = useWatchlist ();
if ( loading ) return < LoadingSpinner /> ;
const byStatus = {
'want-to-watch' : watchlist . filter ( i => i . progressStatus === 'want-to-watch' ),
watching: watchlist . filter ( i => i . progressStatus === 'watching' ),
finished: watchlist . filter ( i => i . progressStatus === 'finished' ),
};
return (
< div >
< section >
< h2 > Plan to Watch ( { byStatus [ 'want-to-watch' ]. length } ) </ h2 >
< MediaGrid items = { byStatus [ 'want-to-watch' ] } />
</ section >
< section >
< h2 > Currently Watching ( { byStatus . watching . length } ) </ h2 >
< MediaGrid items = { byStatus . watching } />
</ section >
< section >
< h2 > Completed ( { byStatus . finished . length } ) </ h2 >
< MediaGrid items = { byStatus . finished } />
</ section >
</ div >
);
}
Implementation Notes
Automatic Sync : The hook automatically switches between local storage and Convex based on authentication state
Sorted Results : Items are always sorted by updated_at in descending order (most recent first)
Real-time Updates : Authenticated users receive real-time updates via Convex subscriptions
Type Safety : Full TypeScript support with WatchlistItem type
Memory Storage Fallback : Uses in-memory storage in SSR/non-browser environments
Source
Location : ~/workspace/source/src/hooks/usewatchlist.ts:350-376
The hook uses a Zustand store with localStorage persistence for guest users and Convex queries for authenticated users.