DEV Community

Idan Bakal
Idan Bakal

Posted on

Stop Over Engineering Your Frontend State: A Developer's Guide to "Less is More"

Choosing a state management library feels a bit like walking into a candy shop. You've got Zustand, Redux Toolkit, Recoil, XState, and good old React Context. It’s easy to get caught up in the hype and introduce a massive library for a project that honestly doesn't need it.

Let’s talk about how to audit your state management and why "Less is More" should be your guiding principle for your next React + TypeScript project.

The Red Flag: "Where did this prop come from?"
We’ve all been there. You open a component, look at the props, and see something like this:

This is Prop Drilling hell, and the natural instinct is to run straight into the arms of a global state manager. But wait—before you npm install, let's break down the types of state you actually have.

  1. Server State vs. Client State (The Great Divide) 90% of the data in your frontend application usually comes from an API. If you are fetching user profiles, financial data, or product lists, that is not client state. That is Server State.

If you use a tool like TanStack Query (React Query) or framework built-ins (like Server Components), you don't need Redux or Zustand for this data.

💡 The Rule: If it lives in a database and you're just fetching it, let your data-fetching library cache it. Keep it out of your global UI store.

  1. UI State: Local is King Does the entire application need to know that a specific dropdown menu is open? No.

Keep your UI state as local as possible. If two sibling components need to share a tiny piece of layout state (like a sidebar toggle), React Context combined with a clean TypeScript interface is perfectly fine.

When Do You Actually Need Global State?
So, when should you use something like Zustand?

You need a dedicated global state manager when you have complex, highly interactive client-side logic that crosses multiple unrelated domains. Examples include:

A complex multi-step form wizard where users can go back and forth.

A dynamic dashboard builder where widgets interact with each other in real-time.

An e-commerce shopping cart with complex discount rules calculated on the fly.

For these cases, a lightweight store like Zustand shines because it doesn't wrap your app in providers and plays beautifully with TypeScript inference.

Summary Checklist for Your Next Project
Before writing your next line of code, ask yourself:

Is this data from an API? ➡️ Use TanStack Query / Server State.

Is this data just for this component/page layout? ➡️ Use useState or Context.

Is this dynamic, complex client logic shared globally? ➡️ Reach for Zustand.

By isolating your server state from your UI state, your code becomes modular, much easier to type with TypeScript, and significantly faster to debug.

Let's Chat! 💬
What’s your go-to state management stack? Are you team "No-Global-Store" or team Zustand? Let’s discuss in the comments below! 👇

Top comments (1)

Collapse
 
marcusykim profile image
Marcus Kim

The most useful boundary here is treating API-backed data as server state and letting TanStack Query own caching instead of pushing profiles and product lists into Redux or Zustand. Keeping a dropdown local while reserving Zustand for cases like a multi-step form or a cart with discount rules gives the architecture a decision rule, not just a library preference. The founder/engineer wrinkle is that these boundaries should follow change ownership: if one feature owns the state, keep it local; if several workflows must coordinate it, centralize it deliberately, because a lightweight store can still become a hidden domain layer.