# Manage State like a Pro: Frontend Edition

> Source: <https://dev.to/peterintech/manage-state-like-a-pro-frontend-edition-1id0>
> Published: 2026-08-20 16:32:06+00:00

AI models write code better and faster than devs, but what AI cannot do reliably for your specific product is make the fundamental *system design decisions like*:

"Where should this piece of data actually live, who owns it, and what happens to the user experience when the network fails, the user refreshes, or a link is shared?"

The modern frontend engineer’s primary value has shifted from *writing code* to *evaluating trade-offs*.

If you put every piece of data into a global Redux store or local `useState`

, you aren't just writing messy code, you are making a system design error. To build fast, resilient interfaces, you need to treat state management as a distribution problem across three distinct domains: **Server State**, **Client State**, and **URL State**.

You must have come across this type of code before...

```
// Wrong: Blending 3 different types of state into one

const [products, setProducts] = useState([]); // Server Data
const [searchQuery, setSearchQuery] = useState(""); // URL/Navigation Intent
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false); // Local UI

useEffect(() => {
  // Syncing server state based on local client variables
  fetchProducts(searchQuery).then(setProducts);
}, [searchQuery]);
```

When you mix these together, your application breaks down in subtle, frustrating ways:

To fix this, we need to categorize data by **ownership and lifecycle**, not just syntax.

```
┌─────────────────┬───────────────────────────────┬──────────────────────────────────┐
│ State Category  │ What It Really Is             │ Best Tooling / Pattern           │
├─────────────────┼───────────────────────────────┼──────────────────────────────────┤
│ 1. Server State │ External data owned by the DB │ Next.js Cache, TanStack Query,   │
│                 │ (Async, cached, shared)       │ RTK Query, SWR                   │
├─────────────────┼───────────────────────────────┼──────────────────────────────────┤
│ 2. URL State    │ Navigation intent             │ `searchParams`, React Router,    │
│                 │ (Shareable, persistent)       │ `window.location`                │
├─────────────────┼───────────────────────────────┼──────────────────────────────────┤
│ 3. Client State │ Ephemeral UI memory           │ `useState`, `useReducer`,        │
│                 │ (Local, temporary, interactive)│ Zustand, React Context            │
└─────────────────┴───────────────────────────────┴──────────────────────────────────┘
```

*The Mental Model:* Server state is data that lives on a remote database (user profiles, product lists, checkout carts). You do *not* own this data on the client - you're merely borrowing a temporary snapshot of it.

Because server state is asynchronous and shared across multiple users, its main challenges are **staleness, caching, deduplication, and revalidation.**

`useState`

or traditional Redux stores. Use dedicated server-state tools like 

```
// Right: Server state managed by a tool built for caching & revalidation
const { data: user, isLoading } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  staleTime: 1000 * 60 * 5, // 5 minutes before re-fetching
});
```

**The Mental Model:** URL state is the input parameter for your entire screen. It represents **navigation intent**, what specific view, page, or filtered slice of data the user wants to see right now.

If a user configures a complex search filter on an e-commerce site, that view should exist as a shareable link. If you trap those filters inside React’s `useState`

, you strip the web of its best native feature: **the link.**

`?search=shoes&page=2`

).

```
// URL State as the single source of truth for view parameters
export default async function CatalogPage({ searchParams }) {
  const { category, page } = await searchParams;

  // The server/API reads directly from the URL context
  const products = await getProducts({ category, page: Number(page) || 1 });

  return <ProductGrid products="{products}"/>;
}
```

**The Mental Model:** Client state is purely local, temporary UI memory. It does not come from a database, and it doesn't need to survive a page share or a browser refresh.

If the user closes the tab, this data can safely disappear without ruining their experience.

`useState`

or `useReducer`

. If it needs to be accessed globally across unrelated components (like a sidebar collapse state), use a lightweight client store like 

```
// Simple, isolated local client state
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
```

When designing a feature, ask yourself these three sequential questions to determine where state belongs:

```
                     [New Piece of State]
                              │
              Does it come from an API/Database?
             ┌────────────────┴────────────────┐
            YES                                NO
             │                                 │
     (1. SERVER STATE)              Should this view be shareable 
  Use: Next.js / TanStack           or survive a page refresh?
                                   ┌───────────┴───────────┐
                                  YES                      NO
                                   │                       │
                            (2. URL STATE)          (3. CLIENT STATE)
                          Use: searchParams        Use: useState / Zustand
```

`/products?category=shoes&q=nike`

). If the user emails this link to a friend, the friend sees the exact same Nike shoes.Writing syntax is rapidly becoming an automated commodity. You can prompt an AI tool to write a complex reducer or a fetch handler in seconds.

However, choosing **where state lives**, **how it expires**, and **how it travels across the network** requires engineering judgment.

By cleanly separating your architecture into **Server State** (remote truth), **URL State** (navigation truth), and **Client State** (interactive truth), you build applications that are inherently faster, far more resilient, and built to scale.
