Skip to main content

Overview

Film Fanatic provides a powerful search experience with real-time results, multi-category filtering, and advanced options for refining search queries.

Search Interface

Location: src/routes/search.tsx

Search Bar Component

Location: src/components/ui/search-bar.tsx The search bar is prominently featured on:
  • Homepage - Main entry point for discovery
  • Search Results Page - Persistent at top for query refinement
  • All Pages - Via navigation (if implemented)

Features

  • Debounced input (500ms default)
  • Real-time URL updates
  • Clear button (X icon)
  • Loading spinner during fetch
  • Auto-focus option
  • Keyboard navigation (Escape to clear)

Behavior

  • Updates URL query parameter
  • Triggers search after delay
  • Preserves query on page reload
  • Navigate directly to results
  • Form submission support

Search Bar Props

interface SearchBarProps {
  query?: string;              // Initial/current query
  placeholder?: string;        // Placeholder text
  isLoading?: boolean;        // Show loading spinner
  isClearable?: boolean;      // Show clear button (default: true)
  onChange?: (value) => void;  // On input change
  onClear?: () => void;        // On clear clicked
  onSubmit?: (value) => void;  // On form submit
  debounceDelay?: number;     // Delay in ms (default: 500)
  autoFocus?: boolean;        // Auto-focus on mount
  disabled?: boolean;         // Disable input
  updateUrlOnChange?: boolean; // Auto-update URL (default: false)
}
Location: src/components/ui/search-bar.tsx:11-24

Search Results Page

URL Structure

/search?query=inception&page=2
Parameters:
  • query - Search term (optional, shows trending if absent)
  • page - Page number (optional, defaults to 1)
Validation: src/routes/search.tsx:32-35

Page States

When visiting /search without a query parameter:
  • Shows “Trending Now” section
  • Displays today’s trending content
  • Encourages users to start searching
  • Search bar is empty and ready for input
Location: src/routes/search.tsx:159-197

Search Categories

Multi-Category Results

Search queries return results from multiple categories:

Movies

Feature films and documentaries

TV Shows

Series, miniseries, talk shows

People

Actors, directors, crew members(Filtered out by default)

Filtering by Type

Users can refine results by media type:
Shows both movies and TV shows (people excluded).Button state: type === null
Implementation: src/routes/search.tsx:286-309 Filter Logic:
const filteredData = data.results.filter((item) => {
  if (item.media_type === "person") return false;  // Exclude people
  if (type && item.media_type !== type) return false;  // Type filter
  if (minRating > 0 && item.vote_average < minRating) return false;  // Rating filter
  return true;
});
Location: src/routes/search.tsx:92-102

Advanced Filtering

Minimum Rating Filter

Users can filter by minimum TMDB rating:
Show all results regardless of rating.Value: "0"
Implementation: src/routes/search.tsx:311-332

Auto-Clear Filters

If filters become too restrictive (zero results):
useEffect(() => {
  if (type && filteredData.length === 0 && data?.results?.length) {
    setType(null);        // Clear type filter
    setMinRating("0");    // Reset rating filter
  }
}, [filteredData.length, type, data?.results?.length]);
Location: src/routes/search.tsx:104-110
Auto-clearing prevents users from getting stuck with impossible filter combinations. They can always reapply filters.

Search Query

API Integration

Location: src/lib/queries.ts:getSearchResult() Search uses TMDB’s multi-search endpoint:
const { data, isFetching, isLoading } = useQuery({
  queryKey: ["search", query, page],
  queryFn: () => getSearchResult(query, page),
  enabled: !!query,  // Only fetch if query exists
  staleTime: 1000 * 60 * 60 * 24,  // 24 hours
  gcTime: 1000 * 60 * 60 * 24,
  retry: 2,
  refetchOnWindowFocus: false,
});
Location: src/routes/search.tsx:63-71

Result Structure

interface SearchResult {
  id: number;
  media_type: "movie" | "tv" | "person";
  title?: string;           // Movies
  name?: string;            // TV shows, people
  poster_path?: string;     // Movies, TV
  profile_path?: string;    // People
  vote_average?: number;    // Rating
  release_date?: string;    // Movies
  first_air_date?: string;  // TV
  overview?: string;        // Description
}

Pagination

Location: src/components/ui/pagination.tsx

Pagination Controls

Previous

Navigate to previous page.Disabled on page 1.

Page Numbers

Current page indicator.Format: “Page X of Y”

Next

Navigate to next page.Disabled on last page.

Pagination Limits

TMDB imposes pagination limits:
const MAX_PAGINATION_LIMIT = 500;  // Maximum pages
const totalPages = Math.min(data?.total_pages ?? 0, MAX_PAGINATION_LIMIT);
Location: src/constants.ts and src/routes/search.tsx:114-155
const handlePageChange = (newPage: number) => {
  setIsPending(true);  // Show loading state
  navigate({
    to: "/search",
    search: { query, page: newPage }
  });
};
  • Updates URL query parameter
  • Triggers new API request
  • Preserves search query
  • Scrolls to top (browser default)
Location: src/routes/search.tsx:112-130

Result Display

Media Cards

Location: src/components/media-card.tsx Each result is displayed as a card with:
  • Poster image (or placeholder)
  • Aspect ratio preserved
  • Hover effects
  • Loading skeleton during fetch

Grid Layout

.HORIZONTAL_MEDIA_GRID_CLASS {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 1rem;
}
Responsive grid that adapts to screen size:
  • Mobile: 2-3 columns
  • Tablet: 4-5 columns
  • Desktop: 6+ columns
Location: src/constants.ts

Debounced Input

To reduce API calls and improve performance:
1

User Types

User enters characters in search bar.
2

Debounce Timer Starts

500ms countdown begins (configurable).
3

User Continues Typing

Timer resets on each keystroke.
4

Timer Completes

500ms of inactivity triggers:
  • URL update with new query
  • API request with search term
5

Results Display

Results appear without manual submission.
Implementation: src/components/ui/search-bar.tsx:47-84

Manual Submission

Users can also press Enter to immediately submit:
const handleSubmit = (e: FormEvent) => {
  e.preventDefault();
  if (debounceTimeout) clearTimeout(debounceTimeout);  // Cancel pending
  navigate({ to: "/search", search: { query: value.trim() } });
};
Location: src/components/ui/search-bar.tsx:111-144 When no search query is provided, the page shows trending content:
const { data: trendingData } = useQuery({
  queryKey: ["trending"],
  queryFn: () => getMedia({ type: "trending_day", page: 1 }),
  enabled: !query,  // Only fetch when no query
});

Trending Now

Today’s most popular movies and TV shows.

Discovery Prompt

Encourages users to search for specific content.
Location: src/routes/search.tsx:73-81

Result Metadata

Total Results Counter

Displayed in top-right of results:
<span className="ml-auto text-[10px] tracking-wider text-muted-foreground">
  Total results: {data?.total_results ?? 0}
</span>
Shows the total number of matches across all pages, not just current page. Location: src/routes/search.tsx:334-336

Search Performance Optimizations

Query Caching

Results cached for 24 hours:
staleTime: 1000 * 60 * 60 * 24
Repeated searches for the same query are instant.
Location: src/routes/search.tsx:67-70

Pending State

Prevents multiple simultaneous requests:
const [isPending, setIsPending] = useState(false);

const handlePageChange = (newPage: number) => {
  if (isPending) return;  // Block if already loading
  setIsPending(true);
  // ... navigate
};
Location: src/routes/search.tsx:60, 120

Keyboard Navigation

Supported Keys

Escape

Clears search input and resets to trending view.Handler: handleKeyDown in SearchBar

Enter

Immediately submits search without waiting for debounce.Handler: handleSubmit in SearchBar
Location: src/components/ui/search-bar.tsx:146-153

Error Handling

When TMDB API fails:
if (error) {
  return (
    <DefaultEmptyState
      message="Something went wrong. Please try again later"
      onReset={() => navigate({ to: "/search" })}
    />
  );
}
User can reset to trending view.Location: src/routes/search.tsx:229-248
Differentiated messages based on context:
  • No matches: “No movies or TV shows found matching your search”
  • Filters too strict: “No movies or TV shows found with the selected filter”
Reset button clears filters or returns to search home.Location: src/routes/search.tsx:256-270

Mobile Responsiveness

  • Full width on mobile
  • Font size: 16px (prevents iOS zoom)
  • Touch-friendly clear button
  • Proper input types for mobile keyboards

Filter Controls

  • Horizontal scroll on mobile
  • Compact button sizes
  • Wrapped layout on small screens

Result Grid

  • Fewer columns on mobile (2-3)
  • Smaller card sizes
  • Optimized image loading

API Reference

Search Query

const { data, isFetching } = useQuery({
  queryKey: ["search", query, page],
  queryFn: () => getSearchResult(query, page),
  enabled: !!query,
});

Filter State

const [type, setType] = useState<FilterType>(null);
const [minRating, setMinRating] = useState("0");

Build docs developers (and LLMs) love