Skip to main content

PodcastList Component

The PodcastList component renders the main podcast browsing interface. It displays a filterable, paginated grid of podcast episodes with search capabilities, multiple filter options, and animated transitions.

Location

src/components/PodcastList/PodcastList.jsx

Import

import PodcastList from "../PodcastList/PodcastList";

Props

onPlayPodcast
function
required
Callback function triggered when a podcast is played. Receives the podcast object as an argument.
(song) => void

Features

Filtering System

The component provides six filter categories:
  • Todos: All podcasts
  • Empezados: Started but not completed episodes
  • No Empezados: Episodes that haven’t been started
  • Favoritos: Favorite episodes
  • Escuchar más tarde: Episodes saved for later
  • Completados: Completed episodes

Search Integration

Filters work in conjunction with the global search term from Redux state:
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;
  }
});

Redux State

The component connects to multiple Redux slices:
const { 
  songs,                  // All podcast episodes
  favoriteEpisodes,       // Array of favorite episode titles
  listenLaterEpisodes,    // Array of listen later episode titles
  searchTerm,             // Current search term
  completedEpisodes       // Array of completed episode titles
} = useSelector((state) => state.podcast);

Animations

The component uses Framer Motion for smooth animations:
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.15
    }
  }
};

Usage Example

import React from "react";
import PodcastList from "./components/PodcastList/PodcastList";
import { useDispatch } from "react-redux";
import { setCurrentPodcast, togglePlay } from "./store/slices/playerSlice";

function App() {
  const dispatch = useDispatch();

  const handlePlayPodcast = (song) => {
    dispatch(setCurrentPodcast(song));
    dispatch(togglePlay(true));
  };

  return (
    <div className="app">
      <PodcastList onPlayPodcast={handlePlayPodcast} />
    </div>
  );
}

Filter Buttons

Each filter button has hover effects and tooltips:
<BootstrapTooltip
  title="Podcasts empezados"
  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 === "empezados" ? styles.activeButton : styles.button
      }
      onClick={() => dispatch(setFilter("empezados"))}
    >
      <Headphones className={styles.headphonesIcon} />
    </motion.button>
  </span>
</BootstrapTooltip>

Pagination

Pagination appears both above and below the podcast grid (when more than 12 items exist):
const indexOfLastSong = currentPage * songsPerPage;
const indexOfFirstSong = indexOfLastSong - songsPerPage;
const currentSongs = filteredSongs.slice(indexOfFirstSong, indexOfLastSong);

// Render pagination
<Pagination
  currentPage={currentPage}
  setCurrentPage={(page) => dispatch(setCurrentPage(page))}
  songsPerPage={songsPerPage}
  songs={filteredSongs}
/>
Clicking a podcast card navigates to its detail page:
const handleCardClick = (song) => {
  navigate(`/podcast/${slugify(song.title)}`);
};

Empty State

When no results match the current filter/search:
{filteredSongs.length === 0 ? (
  <NoResults searchTerm={searchTerm} currentFilter={currentFilter} />
) : (
  // Render podcast grid
)}

SEO

The component includes SEO metadata via React Helmet:
<Helmet>
  <title>
    Nadie Sabe Nada | Podcast de Humor y Comedia con Andreu Buenafuente y Berto Romero
  </title>
</Helmet>

Mobile Responsiveness

The component uses the useMobileDetect hook to adjust spacing:
const isMobile = useMobileDetect();

<motion.div
  style={{
    display: "flex",
    maxHeight: "2rem",
    justifyContent: "center",
    marginBottom: "2.5rem",
    gap: isMobile ? "0.1rem" : "0.3rem"
  }}
>
  • MP3Player - Individual podcast cards rendered in the grid
  • Pagination - Pagination controls for browsing episodes
  • NoResults - Empty state display when no episodes match filters

Build docs developers (and LLMs) love