Moodify-music-playlist

Hey Dev.to community!
I recently added URL state management to my Spotify-inspired music app, Moodify, and I’m blown away by how simple and powerful it is. If you’ve ever wanted your app’s state to be shareable, bookmarkable, and stateless without extra libraries, let me show you why URL state management is a game-changer. Let’s dive in with a quick example from my project!, but first.

Why URL State Management is Wonderful?

Imagine: you’re using a music app, and you filter songs by a “Happy” mood. You want to share that exact view with a friend. Without URL state, you’d need to manually tell them to select “Happy” after opening the app. But with URL state management, you can share a link like https://moodify-drab.vercel.app/?mood=Happy, and—bam!, they land on the same view instantly. Here’s why it’s awesome:

🔹 Shareable Links: Users can copy-paste URLs to share exact app states.

🔹 Bookmarkable: Refresh the page, and the state persists & no local state needed.

🔹 Stateless: Reduces reliance on Context or local state, minimizing re-renders.

I implemented this in Moodify using useSearchParams from react-router-dom, and the result is magical. Let’s see it in action!

✏️// MoodCategories.tsx (Before)
const MoodCategories = ({ moods }) => {
  const { selectedMood, setSelectedMood } = useMoodify();

  return (
    <div>
      {moods.map((mood) => (
        <button
          key={mood}
          onClick={() => setSelectedMood(mood)}
          className={selectedMood === mood ? 'active' : ''}
        >
          {mood}
        button>
      ))}
    div>
  );
};

🔗 URL: https://moodify-drab.vercel.app

Problem: Clicking “Happy” updated selectedMood in Context, but the URL stayed the same. Sharing the link wouldn’t preserve the “Happy” filter, and refreshing the page reset it to “All”.

🌐 URL State Management with useSearchParams

I refactored Moodify to use useSearchParams to store the selected mood in the URL. The state now lives in the the query param ?mood=, making it shareable and persistent. Here’s the updated flow:

Step 1: Read and Write URL State in Home.tsx
I used useSearchParams to manage the mood query param and sync it with MoodifyProvider.

✏️// Home.tsx
import { useSearchParams } from 'react-router-dom';
import { useMoodify } from '../hooks/useMoodify';
import { useEffect } from 'react';
import MoodCategories from '../component/MoodCategories';

const Home = () => {
  const { moods, setMood, thumbnailSongs } = useMoodify();
  const [searchParams, setSearchParams] = useSearchParams();

  const currentMood = (searchParams.get('mood') as Categories) || 'All';

  const HandleMoodSelect = (newMood) => {
    setSearchParams({ mood: newMood });
  };

  useEffect(() => {
    setMood(currentMood);
  }, [setMood, currentMood]);

  return (
    <div>
      <MoodCategories
        moods={moods}
        currentMood={currentMood}
        onMoodSelect={HandleMoodSelect}
      />
      ...
    div>
  );
};

Step 2: Update MoodCategories to Use URL State

I passed the URL-derived currentMood and HandleMoodSelect to MoodCategories as props, removing the dependency on Context for category selection.

✏️// MoodCategories.tsx
const MoodCategories = ({ moods, currentMood, onMoodSelect }) => {
  return (
    <div className="flex gap-2">
      {moods.map((mood) => (
        <button
          key={mood}
          onClick={() => onMoodSelect(mood)}
          className={`border-1 rounded-full ${
            currentMood === mood ? 'bg-green-500 text-white' : 'bg-gray-200'
          }`}
        >
          {mood}
        button>
      ))}
    div>
  );
};

🔗 URL Before: https://moodify-drab.vercel.app

🔗 URL After: https://moodify-drab.vercel.app/?mood=Happy

🧩 Result: Clicking “Happy” updates the URL to ?mood=Happy. Share that link, and anyone can open the app with “Happy” songs pre-filtered. Refresh the page! it still works!

🎉 Bonus: Handling Invalid Moods
What if someone types ?mood=horror (not a valid mood)? I added validation to default to “All” and show a toast notification:

✏️// Home.tsx (Validation)
const urlMood = searchParams.get('mood') as Categories;
const currentMood = moods.includes(urlMood) ? urlMood : 'All';

useEffect(() => {
  if (urlMood && !moods.includes(urlMood)) {
    notify('Invalid category, defaulting to All');
    setSearchParams({ mood: 'All' });
  }
  setMood(currentMood);
}, [urlMood, moods, setMood, notify, setSearchParams]);

🔗 Complete Code: Github

🔄 Finally!
Url state management made Moodify feel more professional and user-friendly. It’s a small change with a big impact—your users will love being able to share and bookmark their app state. Plus, it reduces Context usage, which means fewer unnecessary re-renders.