Overview
The Nadie Sabe Nada podcast app includes a comprehensive search and filtering system that helps you find episodes quickly. The system combines real-time text search with category filters to narrow down your podcast library.
Search Functionality
Real-Time Search
The search bar performs instant filtering as you type, with no need to press enter or click a search button.
Search results update in real-time as you type, providing immediate feedback.
Search Implementation
The search functionality is implemented in the main App component:
const handleSearchChange = (e) => {
dispatch(setFilter("todos"));
dispatch(setSearchTerm(e.target.value));
};
const handleClear = () => {
dispatch(setSearchTerm(""));
};
The search field appears on the home page with custom styling:
<TextField
value={searchTerm}
onChange={handleSearchChange}
placeholder="Buscar Podcast..."
variant="outlined"
className={styles.searchInput}
fullWidth
size="small"
sx={{
"& .MuiInputBase-input": {
color: "black",
fontWeight: "bold"
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#16DB93",
borderRadius: "60px"
},
"&:hover fieldset": {
borderColor: "#16DB93",
borderRadius: "60px"
},
"&.Mui-focused fieldset": {
borderColor: "#16DB93",
borderRadius: "60px"
}
}
}}
InputProps={{
endAdornment: searchTerm && (
<InputAdornment position="end">
<IconButton onClick={handleClear}>
<ClearIcon />
</IconButton>
</InputAdornment>
)
}}
/>
The clear button (X) appears automatically when you start typing, allowing you to quickly reset your search.
Search Behavior
- Case Insensitive - Searches match regardless of capitalization
- Partial Matching - Results include any episodes containing the search term
- Title Search - Currently searches only episode titles
- Auto-Filter Reset - Automatically switches to “All” filter when searching
Filter System
Available Filters
The application provides six category filters:
- Todos (All) - Shows all episodes
- Empezados (Started) - Episodes with saved playback progress
- No Empezados (Not Started) - Episodes never played
- Favoritos (Favorites) - Episodes marked as favorites
- Escuchar Más Tarde (Listen Later) - Episodes saved for later
- Completados (Completed) - Episodes marked as finished
Filter UI
Filters are displayed as icon buttons with tooltips:
<BootstrapTooltip
title="Todos los Podcasts"
placement="top"
arrow
disableInteractive
TransitionComponent={Fade}
TransitionProps={{ timeout: 600 }}
>
<span>
<motion.button
whileHover={{ scale: 1.1, boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.2)" }}
whileTap={{ scale: 0.95 }}
className={currentFilter === "todos" ? styles.activeButton : styles.button}
onClick={() => dispatch(setFilter("todos"))}
>
<FormatListBulleted className={styles.headphonesIcon} />
</motion.button>
</span>
</BootstrapTooltip>
Filter Icons
| Filter | Icon | Description |
|---|
| All | FormatListBulleted | List icon |
| Started | Headphones | Headphones icon |
| Not Started | HeadsetOff | Headphones off icon |
| Favorites | Favorite | Filled heart icon |
| Listen Later | WatchLater | Clock icon |
| Completed | CheckCircle | Checkmark icon |
The active filter button is highlighted with a different style to show which filter is currently applied.
Combined Search and Filter
Filter Logic
Search and filters work together seamlessly. The filtering logic processes both simultaneously:
const filteredSongs = songs.filter((song) => {
const matchesSearch = song.title.toLowerCase().includes(searchTerm.toLowerCase());
const isStarted = playbackTimes[song.title] > 0;
const isCompleted = completedEpisodes.includes(song.title);
switch (currentFilter) {
case "empezados":
return matchesSearch && isStarted && !isCompleted;
case "no-empezados":
return matchesSearch && !isStarted && !isCompleted;
case "favoritos":
return matchesSearch && favoriteEpisodes.includes(song.title);
case "escuchar-mas-tarde":
return matchesSearch && listenLaterEpisodes.includes(song.title);
case "completados":
return matchesSearch && isCompleted;
default:
return matchesSearch;
}
});
How It Works
Search Term Check
First checks if the episode title contains the search term (case insensitive)
Filter Check
Then applies the selected filter criteria
Combine Results
Only episodes matching BOTH criteria are shown
Display
Filtered results appear instantly
Searching automatically resets the filter to “All”. This ensures you don’t accidentally miss results due to an active filter.
Redux State Management
Filter Slice
The filter state is managed in a dedicated Redux slice:
const filterSlice = createSlice({
name: "filter",
initialState: {
currentFilter: "todos",
currentPage: 1,
songsPerPage: 12
},
reducers: {
setFilter: (state, action) => {
state.currentFilter = action.payload;
state.currentPage = 1;
},
setCurrentPage: (state, action) => {
state.currentPage = action.payload;
}
}
});
Changing filters automatically resets to page 1 to avoid showing empty pages.
Search State
Search term is stored in the podcast slice:
const podcastSlice = createSlice({
name: "podcast",
initialState: {
songs: [],
loading: true,
error: null,
searchTerm: "",
// ... other state
},
reducers: {
setSearchTerm: (state, action) => {
state.searchTerm = action.payload;
},
// ... other reducers
}
});
No Results Handling
When no episodes match the current search and filter combination, a friendly “No Results” component appears:
{filteredSongs.length === 0 ? (
<NoResults searchTerm={searchTerm} currentFilter={currentFilter} />
) : (
<motion.div className={styles.playerList}>
{/* Episode cards */}
</motion.div>
)}
Filtered results are automatically paginated:
const indexOfLastSong = currentPage * songsPerPage;
const indexOfFirstSong = indexOfLastSong - songsPerPage;
const currentSongs = filteredSongs.slice(indexOfFirstSong, indexOfLastSong);
Pagination controls only appear when there are more than 12 filtered results.
{filteredSongs.length > 12 && (
<div className={styles.paginationContainer}>
<Pagination
currentPage={currentPage}
setCurrentPage={(page) => dispatch(setCurrentPage(page))}
songsPerPage={songsPerPage}
songs={filteredSongs}
/>
</div>
)}
Animations
Filters include smooth animations powered by Framer Motion:
const itemVariants = {
hidden: { y: -20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: {
type: "spring",
stiffness: 400,
damping: 12,
mass: 0.95
}
},
hover: {
scale: 1.05,
rotate: 1,
transition: {
type: "spring",
stiffness: 300,
damping: 12
}
}
};
Filter buttons scale up slightly on hover, providing visual feedback for better user experience.
Best Practices
Use Search for Specific Episodes
When you know part of an episode’s name, use search to find it quickly
Use Filters for Browsing
When exploring your library by status, use the category filters
Combine Both
You can search within a filtered category for more precise results
Clear Search
Click the X button or clear the field to see all episodes again
Search Tips
For better search results:
- Search is case-insensitive, so “NADIE” and “nadie” give the same results
- Partial words work - searching “programa” finds “Programa 123”
- Search clears active filters, showing all matching episodes
- Use the clear button (X) to quickly reset your search