← all posts

State management doesn't have to be this complicated

·2 min read ·Frontend · React · State Management

There's a rite of passage in React development where you add Redux to your project at the beginning because everyone says you'll need it. Then you spend two days wiring up action creators, reducers, and selectors for a login form. Then you hate state management.

The problem isn't state management. It's that you reached for the most powerful tool before you needed it.

Start With Local State

useState is not a toy. It's the right tool for state that only one component cares about: form inputs, toggle states, loading indicators, modal visibility. Start there. Don't promote state to a global store until two components need it.

Context Before Global Libraries

When state needs to be shared between a few components that aren't directly connected, React Context is often enough:

const ThemeContext = createContext('light');

function App() {
    const [theme, setTheme] = useState('light');
    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Layout />
        </ThemeContext.Provider>
    );
}

Context re-renders every consumer when the value changes, so it's not great for high-frequency updates. But for app-level config—theme, current user, locale—it's perfect.

Zustand for When You Genuinely Need Global State

When you have real global state that multiple unrelated parts of the app need to read and update, Zustand is my default:

const useStore = create<AppStore>((set) => ({
    user: null,
    setUser: (user) => set({ user }),
}));

No providers, no boilerplate, no ceremony. Just state and setters.

The Actual Rule

Local state → Context → Zustand → Redux. Move to the next level only when the current level becomes genuinely painful. Most apps never need to go past Zustand.