# How to Generate Redux Toolkit Code with Cursor

- Tool: Cursor
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Cursor Free+, React/TypeScript with Redux Toolkit
- Last updated: March 2026

## TL;DR

Cursor can generate Redux Toolkit code but often produces verbose, untyped slices with redundant boilerplate. By adding .cursor/rules/ that specify RTK conventions with createSlice, createAsyncThunk, and TypeScript generics, referencing your existing store configuration with @file, and providing typed patterns for Cursor to follow, you get clean, fully typed Redux slices with minimal code.

## Generating Redux Toolkit code with Cursor

Redux Toolkit simplifies Redux with createSlice, createAsyncThunk, and built-in Immer. However, Cursor sometimes generates older Redux patterns with manual action creators, switch statements, and untyped state. This tutorial configures Cursor to generate modern RTK code with full TypeScript support.

## Before you start

- Cursor installed with a React/TypeScript project
- @reduxjs/toolkit and react-redux installed
- A Redux store already configured
- Basic understanding of Redux Toolkit

## Step-by-step guide

### 1. Create a Redux Toolkit rule for Cursor

Add a project rule that specifies RTK patterns and bans legacy Redux patterns. Include your typed hooks and store configuration so Cursor uses them correctly.

```
---
description: Redux Toolkit conventions
globs: "*slice.ts,*store.ts,*thunk.ts"
alwaysApply: true
---

# Redux Toolkit Rules
- ALWAYS use createSlice for reducer logic (never switch statements)
- ALWAYS use createAsyncThunk for async operations
- ALWAYS use PayloadAction<T> for typed action payloads
- ALWAYS use typed hooks: useAppSelector, useAppDispatch from @/store/hooks
- NEVER use useSelector or useDispatch directly (no type inference)
- NEVER use connect() HOC (use hooks instead)
- NEVER use manual action creators (createSlice generates them)
- Use EntityAdapter for normalized collections
- Use RTK Query for data fetching when possible

## File pattern: src/features/{name}/{name}Slice.ts
## State type exported as {Name}State
## Selector functions exported as select{Property}
```

**Expected result:** Cursor generates modern RTK code with createSlice, typed hooks, and no legacy patterns.

### 2. Generate a complete feature slice with async thunks

Reference your store hooks and an existing slice as a pattern. Ask for a complete feature slice including state, reducers, async thunks, and selectors.

```
@redux-toolkit.mdc @src/store/hooks.ts @src/features/auth/authSlice.ts

Create a complete productsSlice following the same patterns as authSlice:
1. State type: ProductsState with items, loading, error, selectedId, filters
2. Async thunks: fetchProducts, fetchProductById, createProduct, updateProduct, deleteProduct
3. Reducers: setFilters, setSelectedId, clearError
4. Selectors: selectAllProducts, selectProductById, selectLoading, selectError
5. Use PayloadAction<T> for all reducer payloads
6. Handle loading/error states in extraReducers for all thunks

Export everything: reducer (default), actions, thunks, selectors.
```

> Pro tip: Reference an existing slice with @file so Cursor matches your exact typing patterns. This is more reliable than describing the patterns in the prompt.

**Expected result:** Cursor generates a complete typed slice matching your existing patterns with createSlice, createAsyncThunk, and typed selectors.

### 3. Generate typed store hooks for Cursor to reference

Create typed useAppSelector and useAppDispatch hooks that Cursor should always import instead of the untyped react-redux hooks. This ensures type inference works across all generated components.

```
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```

**Expected result:** Typed hooks that Cursor imports in generated components for full type inference.

### 4. Generate a component that consumes the slice

Ask Cursor to generate a React component that uses the typed hooks to interact with the Redux store. Reference both the slice and hooks so Cursor generates proper typed dispatch calls.

```
@redux-toolkit.mdc @src/store/hooks.ts @src/features/products/productsSlice.ts

Create a ProductList component that:
1. Uses useAppSelector to read products, loading, and error state
2. Uses useAppDispatch to dispatch fetchProducts on mount
3. Shows a loading spinner while fetching
4. Shows an error message with retry button on failure
5. Renders a list of products with name, price, and category
6. Dispatches setSelectedId when a product is clicked

Use the typed hooks from @/store/hooks, not raw useSelector/useDispatch.
```

**Expected result:** Cursor generates a component using useAppSelector and useAppDispatch with full type inference from the slice.

### 5. Add RTK Query for data fetching

For new features, RTK Query is often better than createAsyncThunk. Show Cursor how to generate an API slice with typed endpoints.

```
@redux-toolkit.mdc @src/store/store.ts

Create an RTK Query API slice for products:
1. Base URL: /api
2. Endpoints: getProducts (query), getProductById (query), createProduct (mutation), updateProduct (mutation), deleteProduct (mutation)
3. Tag types for cache invalidation: ['Product', 'Products']
4. Generated hooks exported for use in components
5. Proper TypeScript types for all request and response shapes

Add the API reducer and middleware to the store configuration.
```

**Expected result:** Cursor generates a typed RTK Query API slice with auto-generated hooks and cache invalidation.

## Complete code example

File: `src/features/products/productsSlice.ts`

```typescript
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from '@/store/store';

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
}

interface ProductFilters {
  category?: string;
  minPrice?: number;
  maxPrice?: number;
  search?: string;
}

interface ProductsState {
  items: Product[];
  selectedId: string | null;
  filters: ProductFilters;
  loading: boolean;
  error: string | null;
}

const initialState: ProductsState = {
  items: [],
  selectedId: null,
  filters: {},
  loading: false,
  error: null,
};

export const fetchProducts = createAsyncThunk(
  'products/fetchAll',
  async (filters: ProductFilters, { rejectWithValue }) => {
    try {
      const params = new URLSearchParams(filters as Record<string, string>);
      const response = await fetch(`/api/products?${params}`);
      if (!response.ok) throw new Error('Failed to fetch products');
      return (await response.json()) as Product[];
    } catch (err) {
      return rejectWithValue((err as Error).message);
    }
  }
);

const productsSlice = createSlice({
  name: 'products',
  initialState,
  reducers: {
    setFilters: (state, action: PayloadAction<ProductFilters>) => {
      state.filters = action.payload;
    },
    setSelectedId: (state, action: PayloadAction<string | null>) => {
      state.selectedId = action.payload;
    },
    clearError: (state) => {
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchProducts.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchProducts.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchProducts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload as string;
      });
  },
});

export const { setFilters, setSelectedId, clearError } = productsSlice.actions;

export const selectAllProducts = (state: RootState) => state.products.items;
export const selectLoading = (state: RootState) => state.products.loading;
export const selectError = (state: RootState) => state.products.error;

export default productsSlice.reducer;
```

## Common mistakes

- **Cursor generates useSelector instead of useAppSelector** — The untyped useSelector is more common in training data. Without typed hooks, components lose Redux state type inference. Fix: Create typed hooks in src/store/hooks.ts and add a rule: ALWAYS use useAppSelector and useAppDispatch from @/store/hooks.
- **Cursor generates switch-case reducers instead of createSlice** — Older Redux patterns with switch statements dominate training data. Cursor may revert to them without explicit rules. Fix: Add ALWAYS use createSlice and NEVER use switch statements for reducers to your rules.
- **Missing error handling in createAsyncThunk** — Cursor sometimes generates thunks without try/catch or rejectWithValue, causing unhandled rejections and unclear error states. Fix: Include error handling patterns in your rules with rejectWithValue for all async thunks.

## Best practices

- Create typed hooks (useAppSelector, useAppDispatch) and reference them in rules
- Use createSlice for all reducer logic with PayloadAction<T> for typed payloads
- Use createAsyncThunk with rejectWithValue for proper error handling
- Reference an existing slice with @file when generating new features
- Export selectors alongside the slice for colocation
- Consider RTK Query for data fetching instead of manual async thunks
- Keep slice files under 150 lines by extracting complex thunks to separate files

## Frequently asked questions

### Should I use RTK Query or createAsyncThunk?

RTK Query for standard CRUD data fetching. createAsyncThunk for complex async logic with side effects like multi-step workflows, file uploads, or operations that update multiple slices.

### How do I handle normalized state with Cursor?

Ask Cursor to use createEntityAdapter from RTK. Reference the adapter documentation in your rules and provide an example of the entity adapter pattern.

### Can Cursor generate RTK Query code?

Yes. Reference your store configuration and ask for an API slice with createApi and fetchBaseQuery. Cursor generates typed endpoints with auto-generated hooks.

### What about Zustand or Jotai instead of Redux?

If your app does not need Redux's middleware, devtools, or time-travel debugging, Zustand is simpler. Create separate rules for your chosen state management library.

### How do I test Cursor-generated slices?

Ask Cursor to generate tests that dispatch actions and verify state changes. RTK slices are pure functions, making them easy to test without mocking.

### Can RapidDev help with state management architecture?

Yes. RapidDev evaluates state management needs and implements the right solution, whether Redux Toolkit, Zustand, or React Context, with Cursor rules for ongoing consistency.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-use-cursor-ai-to-generate-typed-redux-toolkit-slices-with-minimal-boilerplate
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-use-cursor-ai-to-generate-typed-redux-toolkit-slices-with-minimal-boilerplate
