DEV Community

Ntty
Ntty

Posted on

Stop Over-Engineering Your State Management

The 'Store' Trap

I spent three years of my career thinking that every single piece of data in a web app belonged in a global store. Whether it was a user profile, a theme toggle, or a simple 'is loading' boolean for a single button, it all went into Redux or Vuex.

I thought this was 'professional' architecture. In reality, I was just creating a massive, tangled web of boilerplate that made the codebase impossible to navigate.

When you put everything in a global store, you lose the ability to reason about your components in isolation. You start treating your frontend like a database rather than a UI.

The Hierarchy of State

To keep a project maintainable, you need to be strict about where state lives. I now follow a simple hierarchy of needs.

1. Local Component State

If the data is only used by one component, keep it there. This includes form inputs, toggle switches, and hover states.

One common mistake is lifting state up too early. Developers often move state to a parent component because they think it might be needed elsewhere later. This creates 'prop drilling', where you pass data through five layers of components that do not even use it. Resist this. Keep state as low as possible.

2. Composition and Slots

Before reaching for a global store to solve prop drilling, try component composition. Instead of passing data down, pass the component itself as a child.

By passing a pre-configured component into a wrapper, you remove the need for the middleman components to know about the data. This keeps your components decoupled and makes them much easier to test.

3. Context or Provide/Inject

Context is for data that is truly global but rarely changes. Think of the current user's authentication status, the app locale, or a theme.

Context is not a replacement for a state management library. The biggest issue with Context is that it triggers a re-render of all consumers whenever any value in the provider changes. If you put high-frequency updates (like a timer or a text input) in Context, your app will lag.

4. Dedicated State Stores

Use a store (like Zustand, Pinia, or Redux) only when you have complex data that is shared across distant parts of the application and updates frequently.

Examples include a shopping cart, a complex dashboard with multiple synced widgets, or a real-time chat system. If you cannot explain why the data needs to be global in one sentence, it probably does not belong in a store.

Dealing with Server State

Here is the biggest realization I had: most of what we call 'state' is actually just a cache of data from the server.

Fetching a list of users and putting it in a global store is often a waste of time. You have to manually manage loading states, error states, and cache invalidation.

Instead, use a dedicated server-state library. These tools handle caching, re-fetching on window focus, and loading states automatically. This removes about 50 percent of the code in most global stores because you no longer need to track 'isLoading' or 'isError' for every single API call.

Concrete Takeaway

Next time you are about to add a new piece of state, ask yourself these three questions in order:

  1. Can this live inside a single component?
  2. Can I solve this by changing how I nest my components (composition)?
  3. Is this actually server data that just needs to be cached?

If the answer to all three is 'no', only then should you reach for a global store. Your future self, and anyone else who has to read your code, will thank you for the simplicity.

Top comments (0)