DEV Community

Ntty
Ntty

Posted on

Stop Using Complex State Managers for Every Small Project

The State Management Trap

I spent three years of my career adding a global state library to every single project I touched. Whether it was a simple contact form or a massive dashboard, the pattern was the same. I would install a library, set up a store, define actions, and create a series of boilerplate files before I even wrote the actual feature code.

I thought this was the professional way to build. I believed that preparing for scale meant adding infrastructure upfront. In reality, I was just adding cognitive load and slowing down my development speed.

The Cost of Boilerplate

When you use a heavy state manager for a small app, you pay a tax in three areas: bundle size, debugging time, and mental overhead.

First, the bundle size. Adding a few more kilobytes might not seem like much, but when you add a store, a middleware layer, and several helper libraries, it adds up. For a landing page or a small tool, this is wasteful.

Second, the debugging loop. Instead of looking at a component and seeing where the data comes from, you have to jump between the component, the action creator, the reducer, and the store configuration. You spend more time navigating files than fixing bugs.

Third, the mental overhead. Every new developer joining the project has to learn your specific store architecture before they can change a single button color.

The Hierarchy of State

Most developers treat state as one big bucket. The trick is to categorize your state based on who needs to know about it.

  1. Local State: Only one component cares. Use a simple hook or local variable. If you are passing a prop down only one level, just keep it there.

  2. Lifted State: Two or three sibling components need the data. Lift the state to the nearest common parent. This is the most underused pattern because people jump straight to global stores.

  3. Global State: The entire app needs this (like user authentication or theme settings). This is the only place where a dedicated library or a Context provider makes sense.

  4. Server State: Data that comes from an API. This is not actually global state. It is a cache of server data. Using a global store to manage API responses often leads to bugs where the UI is out of sync with the database.

A Better Workflow

Start with the simplest possible solution. I now follow a rule of three.

If I find myself passing a prop through more than three levels of components (prop drilling), I stop. Only then do I consider a Context provider or a lightweight state library.

For server data, I stopped putting API responses in a global store. Instead, I use dedicated data fetching hooks. These handles caching, loading states, and re-validation automatically. This removes about 40 percent of the code I used to write manually in reducers.

When to Actually Use a Library

I am not saying global state managers are useless. They are great for specific scenarios.

Use them when you have complex, interdependent state transitions. For example, a collaborative map editor or a complex spreadsheet app where changing one cell triggers updates in ten other distant parts of the UI. In those cases, the structured nature of a store prevents the app from becoming a mess of callbacks.

For everything else, the built-in tools are usually enough.

The Concrete Takeaway

Next time you start a project, forbid yourself from installing a state management library for the first week. Use local state, lift state up when needed, and use a Context provider for global settings.

If you find yourself genuinely struggling to track data flow after a week of building, then add the library. You will likely find that you didn't need it, and your codebase will be much cleaner for it.

Top comments (0)