I gave Cursor free reign to build a React app. The result? Surprisingly solid code.
The Setup
I decided to build a project management app using Cursor (AI coding assistant). The goal: see how well AI can handle React state management.
Challenge: Which state library would AI understand best?
The Contenders
- Redux - Powerful but verbose
- Zustand - Minimal but simple
- MobX - Class-based but decorators
- easy-model - The unknown
The Test
I gave Cursor the same prompt for each:
"Build a user management page with CRUD operations, search filtering, and pagination."
Let's see what it generated.
Redux Version
Cursor generated:
- 3 action files
- 2 reducer files
- 1 selector file
- 1 hook file
- Component code
Then it got confused. Which action does what? Where should this logic go?
Result: ~300 lines, but messy.
Zustand Version
const useStore = create((set) => ({
users: [],
loading: false,
filter: "",
setUsers: (users) => set({ users }),
setFilter: (filter) => set({ filter }),
}));
Clean, but Cursor struggled with:
- Where to put business logic?
- How to handle async properly?
- No clear pattern for complex scenarios
Result: ~80 lines, but incomplete.
MobX Version
Cursor loved the class syntax at first:
class UserStore {
@observable users = [];
@action async fetchUsers() {
/* ... */
}
}
But then TypeScript errors started appearing:
- "Decorator support is experimental"
- "Property does not exist on type"
- "Cannot find name 'observer'"
Result: ~120 lines, but broken.
easy-model Version
class UserModel {
users: User[] = [];
loading = false;
filter = "";
@loader.load()
async fetchUsers() {
this.loading = true;
const res = await api.getUsers({ filter: this.filter });
this.users = res.data;
this.loading = false;
}
setFilter(filter: string) {
this.filter = filter;
this.fetchUsers();
}
}
Cursor nailed it. First try. No errors.
Result: ~60 lines, fully functional.
Why easy-model Won
1. Class = AI's Native Language
AI models are trained on OOP code. Classes make sense to them.
class UserModel {
// properties = state
users: User[] = [];
// methods = actions
async fetchUsers() {
/* ... */
}
}
2. Templates Work
Cursor could apply templates:
// CRUD template
class CRUDModel {
items: Item[] = [];
@loader.load() async fetchAll() {
/* ... */
}
@loader.load() async create(item: Item) {
/* ... */
}
async update(id: number, item: Item) {
/* ... */
}
delete(id: number) {
/* ... */
}
}
3. No Decorator Confusion
No @observable, @action, @observer. Just plain TypeScript.
4. TypeScript Just Works
Full inference, no any, no type gymnastics.
5. DI Makes Dependencies Clear
const apiSchema = object({ baseUrl: string() });
class UserApi {
@inject(apiSchema)
config?: { baseUrl: string };
}
Cursor knows what this service needs.
The Verdict
| Library | Lines | Errors | AI Understanding |
|---|---|---|---|
| Redux | 300+ | Few | Poor |
| Zustand | 80 | None | Medium |
| MobX | 120 | Many | Good |
| easy-model | 60 | None | Excellent |
What Cursor Built
With easy-model, Cursor built:
- ✅ User CRUD
- ✅ Search filtering
- ✅ Pagination
- ✅ Loading states
- ✅ Error handling
- ✅ Cross-component state sharing
- ✅ Undo/redo capability
All in ~60 lines of readable code.
Conclusion
For AI-assisted coding, easy-model is the clear winner:
- AI understands class-based models
- No boilerplate to confuse AI
- TypeScript works out of the box
- Templates are learnable
- Dependencies are explicit
Result: AI produces working code on the first try.
GitHub: https://github.com/ZYF93/easy-model
npm: pnpm add @e7w/easy-model
⭐️ Star if this experiment surprised you too!
Top comments (0)