# How to stop Cursor from overengineering solutions

- Tool: Cursor
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Cursor Free+, React projects
- Last updated: March 2026

## TL;DR

Cursor defaults to Redux for almost any state management need because Redux appears heavily in its training data. For simple UI state, component-local state, or server cache, lighter solutions like useState, React Context, or SWR are better choices. This tutorial shows how to add state management guidance to .cursorrules so Cursor picks the right tool for each situation.

## Stopping Cursor from overengineering state management

Not every piece of state needs Redux. Cursor suggests Redux for toggle booleans, form inputs, and even server-cached data because it is the most represented state library in its training data. This tutorial teaches Cursor when to use simpler alternatives.

## Before you start

- Cursor installed with a React project
- Basic understanding of React state management options
- Familiarity with useState, Context, and Redux
- Cursor Chat (Cmd+L) access

## Step-by-step guide

### 1. Add state management decision rules

Create rules that map state types to appropriate solutions. This gives Cursor a decision tree instead of always defaulting to Redux.

```
---
description: State management decision guide
globs: "src/**/*.tsx,src/**/*.ts"
alwaysApply: true
---

## State Management Decision Tree
| State Type | Solution | Example |
|-----------|---------|--------|
| Form input, toggle, local UI | useState | Modal open/close, input value |
| Shared UI state (2-3 components) | useState + prop drilling | Active tab shared by siblings |
| Theme, locale, auth | React Context | ThemeProvider, AuthProvider |
| Server data (read-heavy) | SWR or React Query | User list, product catalog |
| Complex client state (10+ actions) | Zustand | Shopping cart, editor state |
| Enterprise (middleware, devtools) | Redux Toolkit | Large team, complex workflows |

## Rules
- NEVER use Redux for state shared by fewer than 5 components
- NEVER use Redux for server-cached data (use SWR/React Query)
- NEVER use Redux for form state (use react-hook-form or useState)
- Default to the SIMPLEST solution that works
- If unsure, ask the user which approach they prefer
```

**Expected result:** Cursor selects the simplest appropriate state solution based on the use case.

### 2. Prompt Cursor for simple state explicitly

When you need local state, mention it explicitly in your prompt. Without guidance, Cursor may still reach for global state management.

```
// Instead of: "Add state management for this modal"
// Say:

// Cursor Chat prompt (Cmd+L):
// Add an open/close toggle for this modal using useState.
// Do NOT use Redux, Context, or any state library.
// The state only needs to live in this component.

// Cursor generates:
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
```

> Pro tip: Be explicit about what NOT to use. Saying 'Do NOT use Redux' is more effective than just asking for useState.

**Expected result:** Simple useState-based state management without any global state library.

### 3. Redirect server state to SWR

When Cursor tries to put API data in Redux, redirect it to SWR or React Query. Server state and client state are fundamentally different concerns.

```
// If Cursor suggests Redux for fetching users:
// "Create a Redux slice for user data with fetchUsers thunk"

// Redirect with:
// Do NOT use Redux for this. User data is server state.
// Use SWR to fetch from /api/users with automatic caching
// and revalidation. Create a useUsers() hook in src/hooks/.

import useSWR from 'swr';
import { fetcher } from '@/lib/fetcher';

export function useUsers() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher);
  return { users: data ?? [], isLoading, isError: !!error };
}
```

**Expected result:** Server data managed by SWR with automatic caching instead of a Redux slice.

### 4. Use Zustand for moderate complexity

When state is too complex for Context but does not need Redux's full machinery, direct Cursor to Zustand. It has a simpler API with built-in performance optimization.

```
// Cursor Chat prompt (Cmd+L):
// Create a shopping cart store using Zustand (NOT Redux).
// State: items array, total, item count.
// Actions: addItem, removeItem, clearCart, updateQuantity.
// Use Zustand's create() function.

import { create } from 'zustand';

interface CartStore {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
  clearCart: () => void;
  total: () => number;
}

export const useCartStore = create<CartStore>((set, get) => ({
  items: [],
  addItem: (item) => set((s) => ({ items: [...s.items, item] })),
  removeItem: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
  clearCart: () => set({ items: [] }),
  total: () => get().items.reduce((sum, i) => sum + i.price * i.qty, 0),
}));
```

**Expected result:** A Zustand store that provides the needed functionality without Redux boilerplate.

### 5. Evaluate when Redux IS appropriate

Redux is the right choice for large teams with complex middleware needs, time-travel debugging requirements, or when you need centralized devtools across many state slices.

```
// Signs you actually need Redux:
// 1. More than 10 state slices with cross-slice dependencies
// 2. Complex middleware (logging, analytics, optimistic updates)
// 3. Large team needing enforced patterns and devtools
// 4. Need time-travel debugging for complex workflows
// 5. Existing Redux codebase that works well
//
// If none of these apply, use a simpler solution.
```

**Expected result:** Clear criteria for when Redux is genuinely the right choice.

## Complete code example

File: `.cursor/rules/state-management.mdc`

```markdown
---
description: State management decision guide
globs: "src/**/*.{ts,tsx}"
alwaysApply: true
---

## State Management Hierarchy (simplest first)

### 1. useState (default for local state)
- Form inputs, toggles, modals, local UI state
- State used by ONE component only
- No boilerplate, no imports beyond React

### 2. useState + props (shared between parent/child)
- State shared by 2-3 closely related components
- Lift state to nearest common parent

### 3. React Context (app-wide static state)
- Theme, locale, authentication status
- State that changes INFREQUENTLY
- Split state/dispatch contexts to avoid re-renders

### 4. SWR or React Query (server state)
- Data fetched from APIs
- Automatic caching, revalidation, deduplication
- NEVER put server data in Redux

### 5. Zustand (complex client state)
- Shopping cart, editor state, multi-step forms
- Simpler API than Redux, built-in performance
- Good for 3-8 state slices

### 6. Redux Toolkit (enterprise complexity)
- 10+ state slices with middleware
- Large teams needing enforced patterns
- Time-travel debugging required

## Anti-patterns to AVOID
- Redux for a single boolean toggle
- Redux for server-cached data (use SWR)
- Redux for form state (use react-hook-form)
- Context for frequently changing state (causes re-renders)
- Global state for component-local concerns
```

## Common mistakes

- **Cursor creating a Redux slice for a modal toggle** — Redux is the most represented state library in training data. Cursor defaults to it even for trivial state. Fix: Add the state management decision tree to .cursorrules. Explicitly state 'Use useState for this' in prompts for simple state.
- **Using Redux for API data that should be cached with SWR** — Cursor generates fetchUsers Redux thunks when SWR/React Query provides better caching, deduplication, and revalidation automatically. Fix: Add 'NEVER put server data in Redux, use SWR' to your rules.
- **Adding Context for frequently updating state** — Context re-renders all consumers on every state change. For frequent updates (typing, scrolling), this causes performance issues. Fix: Use Zustand for frequently updating shared state. It only re-renders components that subscribe to the specific value that changed.

## Best practices

- Default to useState for local state; only escalate when needed
- Use SWR or React Query for server data, never Redux
- Add a state management decision tree to .cursorrules
- Be explicit in prompts about which state solution to use
- Use Zustand as the intermediate step between Context and Redux
- Evaluate Redux only for enterprise needs (10+ slices, middleware, large teams)
- State the constraint 'Do NOT use Redux' when simpler solutions suffice

## Frequently asked questions

### Is Redux ever the right choice?

Yes, for large applications with 10+ state slices, complex middleware needs, large teams needing enforced patterns, or when you need time-travel debugging. It is overkill for most small to medium projects.

### Can I use Zustand and Redux in the same project?

Yes, but it is confusing. Pick one global state library per project. If you have existing Redux and want to add simpler state, Zustand can coexist but the team needs clear guidelines on when to use each.

### How do I migrate from Redux to Zustand?

Convert one slice at a time. Create a Zustand store for the slice, update consumers to use the new store, remove the Redux slice. Ask Cursor to help: '@src/store/slices/cartSlice.ts Convert this Redux slice to a Zustand store.'

### Why does Cursor suggest Redux so often?

Redux has been the dominant state management library since 2015. It has the most tutorials, examples, and documentation in Cursor's training data. The .cursorrules decision tree corrects this bias.

### What about Jotai or Recoil?

Both are good alternatives. Jotai is atom-based (bottom-up), Recoil is similar but less maintained. Add your preferred library to the state management rules if you use them.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-keep-cursor-ai-from-overusing-redux-when-a-simpler-state-solution-is-sufficient
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-keep-cursor-ai-from-overusing-redux-when-a-simpler-state-solution-is-sufficient
