URL Might Be the Best Place to Store State in React — and That’s Not a Joke

June 12, 2025 .2 min read

When you hear “state management in React,” you probably think of useState, useReducer, Redux, Zustand, or that one state library your co-worker swore was "the future." But let me hit you with a curveball:

The URL.

Yep. That thing at the top of your browser? It’s not just a place for copy-pasting YouTube links, it might just be your best state store. Let’s break it down.

Why the URL?

Because it's shareable, bookmarkable, and doesn't vanish when someone hits refresh like useState does. Plus, users already expect it to carry meaning. Ever tried sharing an app state with a friend using local state? Exactly.

Let’s say you’re building a filterable product page:

const [category, setCategory] = useState("laptops");
const [sort, setSort] = useState("price-asc");

Cool, but hit refresh and boom ,everything’s gone. Now imagine this instead:

/products?category=laptops&sort=price-asc

Chef’s kiss. Now you can share that link and everyone sees the same view.

How Do You Actually Use It?

React Router Version:

products-page.tsx

import { useSearchParams } from "react-router-dom";

function ProductsPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  const category = searchParams.get("category") || "all";
  const sort = searchParams.get("sort") || "relevance";

  const updateFilters = (newCategory, newSort) => {
    setSearchParams({ category: newCategory, sort: newSort });
  };

  return (
    <>
      <h1>Showing {category} sorted by {sort}</h1>
      <button onClick={() => updateFilters("phones", "price-desc")}>
        Show Cheap Phones
      </button>
    </>
  );
}

Next.js Version:

src/app/products/page.tsx

"use client";

import { useSearchParams, useRouter } from "next/navigation";

export default function ProductsPage() {
  const searchParams = useSearchParams();
  const router = useRouter();

  const category = searchParams.get("category") || "all";
  const sort = searchParams.get("sort") || "relevance";

  const updateFilters = (newCategory: string, newSort: string) => {
    const params = new URLSearchParams();
    params.set("category", newCategory);
    params.set("sort", newSort);

    router.push(`/products?${params.toString()}`);
  };

  return (
    <>
      <h1>
        Showing {category} sorted by {sort}
      </h1>
      <button onClick={() => updateFilters("phones", "price-desc")}>
        Show Cheap Phones
      </button>
    </>
  );
}

Now your app is refresh-proof, linkable, and way more user-friendly. Your product manager might even buy you a coffee.

Bonus: You Get History for Free

URL state = browser history support. Users can click back and forth like it's 2004 and everything just works. No Redux setup. No context. No Zustand bears.

When Not to Use the URL

  • Sensitive data (don’t leak tokens in URLs, please).
  • Complex objects (serializing nested objects into query params == pain).
  • Temporary UI states (like modal open/closed, no one wants a ?modal=open).

Conclusion

  • If your state needs to survive refreshes, be shareable, or have browser back-button support, put it in the URL.
  • If it’s just “is this dropdown open?”, keep it local.
  • Don’t overthink it. Use the right tool for the job. Sometimes, the right tool is staring you in the address bar.