Skip to main content
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

watchlist
WatchlistItem[]
Array of watchlist items, sorted by most recently updated first. Only includes items where inWatchlist is true.
loading
boolean
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>
  );
}
isOnWatchList
boolean
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>;
}

useMediaState

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>
  );
}
mediaState
WatchlistItem | null
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.

Build docs developers (and LLMs) love