-
Notifications
You must be signed in to change notification settings - Fork 0
⚡ Bolt: Debounce search input to prevent UI freezing during text entry #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { useState, useEffect } from "react"; | ||
|
|
||
| export function useDebounce<T>(value: T, delay: number = 300): T { | ||
| const [debouncedValue, setDebouncedValue] = useState<T>(value); | ||
|
|
||
| useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| setDebouncedValue(value); | ||
| }, delay); | ||
|
|
||
| return () => { | ||
| clearTimeout(timer); | ||
| }; | ||
| }, [value, delay]); | ||
|
|
||
| return debouncedValue; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { Link } from "wouter"; | |
| import { motion } from "framer-motion"; | ||
| import { usePageTitle } from "@/hooks/use-page-title"; | ||
| import { useState, useMemo } from "react"; | ||
| import { useDebounce } from "@/hooks/use-debounce"; | ||
| import { Input } from "@/components/ui/input"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { | ||
|
|
@@ -22,25 +23,26 @@ export default function Dashboard() { | |
| const { data: songs, isLoading } = useSongs(); | ||
|
|
||
| const [searchQuery, setSearchQuery] = useState(""); | ||
| const debouncedSearchQuery = useDebounce(searchQuery, 300); | ||
| const [genreFilter, setGenreFilter] = useState<string>("all"); | ||
| const [moodFilter, setMoodFilter] = useState<string>("all"); | ||
|
|
||
| const filteredSongs = useMemo(() => { | ||
| if (!songs) return []; | ||
|
|
||
| return songs.filter(song => { | ||
| const matchesSearch = searchQuery === "" || | ||
| song.title.toLowerCase().includes(searchQuery.toLowerCase()) || | ||
| song.lyrics.toLowerCase().includes(searchQuery.toLowerCase()); | ||
| const matchesSearch = debouncedSearchQuery === "" || | ||
| song.title.toLowerCase().includes(debouncedSearchQuery.toLowerCase()) || | ||
| song.lyrics.toLowerCase().includes(debouncedSearchQuery.toLowerCase()); | ||
|
Comment on lines
+34
to
+36
|
||
|
|
||
| const matchesGenre = genreFilter === "all" || song.genre === genreFilter; | ||
| const matchesMood = moodFilter === "all" || song.mood === moodFilter; | ||
|
|
||
| return matchesSearch && matchesGenre && matchesMood; | ||
| }); | ||
| }, [songs, searchQuery, genreFilter, moodFilter]); | ||
| }, [songs, debouncedSearchQuery, genreFilter, moodFilter]); | ||
|
|
||
| const hasActiveFilters = searchQuery !== "" || genreFilter !== "all" || moodFilter !== "all"; | ||
| const hasActiveFilters = debouncedSearchQuery !== "" || genreFilter !== "all" || moodFilter !== "all"; | ||
|
|
||
| const clearFilters = () => { | ||
| setSearchQuery(""); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,7 @@ import type { Song } from "@shared/schema"; | |||||
| import { GENRES, MOODS } from "@shared/schema"; | ||||||
| import { usePageTitle } from "@/hooks/use-page-title"; | ||||||
| import { useState, useMemo, memo } from "react"; | ||||||
| import { useDebounce } from "@/hooks/use-debounce"; | ||||||
|
|
||||||
| interface PublicSongCardProps { | ||||||
| song: Song; | ||||||
|
|
@@ -122,6 +123,7 @@ export default function Explore() { | |||||
| const likedIds = likedData?.likedIds || []; | ||||||
|
|
||||||
| const [searchQuery, setSearchQuery] = useState(""); | ||||||
| const debouncedSearchQuery = useDebounce(searchQuery, 300); | ||||||
| const [genreFilter, setGenreFilter] = useState<string>("all"); | ||||||
| const [moodFilter, setMoodFilter] = useState<string>("all"); | ||||||
| const [sortBy, setSortBy] = useState<string>("popular"); | ||||||
|
|
@@ -130,9 +132,9 @@ export default function Explore() { | |||||
| if (!songs) return []; | ||||||
|
|
||||||
| let filtered = songs.filter(song => { | ||||||
| const matchesSearch = searchQuery === "" || | ||||||
| song.title.toLowerCase().includes(searchQuery.toLowerCase()) || | ||||||
| song.lyrics.toLowerCase().includes(searchQuery.toLowerCase()); | ||||||
| const matchesSearch = debouncedSearchQuery === "" || | ||||||
| song.title.toLowerCase().includes(debouncedSearchQuery.toLowerCase()) || | ||||||
| song.lyrics.toLowerCase().includes(debouncedSearchQuery.toLowerCase()); | ||||||
|
Comment on lines
+135
to
+137
|
||||||
|
|
||||||
| const matchesGenre = genreFilter === "all" || song.genre === genreFilter; | ||||||
| const matchesMood = moodFilter === "all" || song.mood === moodFilter; | ||||||
|
|
@@ -149,9 +151,9 @@ export default function Explore() { | |||||
| } | ||||||
|
|
||||||
| return filtered; | ||||||
| }, [songs, searchQuery, genreFilter, moodFilter, sortBy]); | ||||||
| }, [songs, debouncedSearchQuery, genreFilter, moodFilter, sortBy]); | ||||||
|
|
||||||
| const hasActiveFilters = searchQuery !== "" || genreFilter !== "all" || moodFilter !== "all"; | ||||||
| const hasActiveFilters = debouncedSearchQuery !== "" || genreFilter !== "all" || moodFilter !== "all"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Use the immediate Prompt for AI agents
Suggested change
|
||||||
|
|
||||||
| const clearFilters = () => { | ||||||
| setSearchQuery(""); | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.