# How to generate shared state logic with Cursor

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

## TL;DR

Cursor can generate React Context providers with custom hooks when given clear type definitions and state management patterns. This tutorial shows how to prompt Cursor to create type-safe Context APIs with useReducer, memoized selectors, and provider composition, avoiding the common pitfalls of unnecessary re-renders and loosely typed context values.

## Generating shared state logic with Cursor

React Context API combined with custom hooks provides a lightweight alternative to Redux for shared state. Cursor can scaffold entire Context systems quickly, but without guidance it generates untyped context, missing providers, and patterns that cause excessive re-renders. This tutorial establishes a pattern that produces performant, type-safe Context code every time.

## Before you start

- Cursor installed with a React + TypeScript project
- React 18+ for useSyncExternalStore and modern Context features
- Understanding of React Context and useReducer
- Familiarity with Cursor Chat (Cmd+L) and Cmd+K

## Step-by-step guide

### 1. Add Context generation rules to .cursor/rules

Create rules that enforce type-safe Context patterns and prevent common performance pitfalls. These rules ensure every Context Cursor generates follows best practices.

```
---
description: React Context generation conventions
globs: "src/contexts/**/*.tsx,src/providers/**/*.tsx"
alwaysApply: true
---

## Context Rules
- ALWAYS create a typed context with createContext<Type | undefined>(undefined)
- ALWAYS create a custom hook (useXxxContext) that throws if used outside provider
- Split state and dispatch into separate contexts to prevent re-renders
- Use useReducer for contexts with more than 2 state values
- Memoize context values with useMemo to prevent reference changes
- Place contexts in src/contexts/{name}Context.tsx
- Export the provider component and the custom hook, NEVER the raw context
```

**Expected result:** Cursor generates typed, performant Context code following your conventions.

### 2. Generate a typed Context with custom hook

Ask Cursor to generate a complete Context system with provider, custom hook, and type definitions. Reference your rules and specify the state shape.

```
// Cursor Chat prompt (Cmd+L):
// Generate a ThemeContext at src/contexts/ThemeContext.tsx
// State: { theme: 'light' | 'dark', fontSize: number }
// Actions: toggleTheme, setFontSize(size)
// Use useReducer. Split state and dispatch contexts.
// Export ThemeProvider and useTheme hook.

import { createContext, useContext, useReducer, useMemo, ReactNode } from 'react';

type Theme = 'light' | 'dark';
interface ThemeState { theme: Theme; fontSize: number; }
type ThemeAction = 
  | { type: 'TOGGLE_THEME' }
  | { type: 'SET_FONT_SIZE'; payload: number };

const ThemeStateContext = createContext<ThemeState | undefined>(undefined);
const ThemeDispatchContext = createContext<React.Dispatch<ThemeAction> | undefined>(undefined);

function themeReducer(state: ThemeState, action: ThemeAction): ThemeState {
  switch (action.type) {
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_FONT_SIZE':
      return { ...state, fontSize: action.payload };
    default:
      return state;
  }
}

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(themeReducer, { theme: 'light', fontSize: 16 });
  const stateValue = useMemo(() => state, [state]);
  return (
    <ThemeStateContext.Provider value={stateValue}>
      <ThemeDispatchContext.Provider value={dispatch}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeStateContext.Provider>
  );
}

export function useTheme() {
  const state = useContext(ThemeStateContext);
  const dispatch = useContext(ThemeDispatchContext);
  if (!state || !dispatch) throw new Error('useTheme must be used within ThemeProvider');
  return { ...state, toggleTheme: () => dispatch({ type: 'TOGGLE_THEME' }), setFontSize: (size: number) => dispatch({ type: 'SET_FONT_SIZE', payload: size }) };
}
```

> Pro tip: Splitting state and dispatch into separate contexts prevents components that only dispatch from re-rendering when state changes.

**Expected result:** A type-safe Context with split providers, a reducer, and a custom hook with action helpers.

### 3. Generate a provider composition wrapper

When your app has multiple contexts, nesting providers creates deep component trees. Ask Cursor to generate a composition utility that flattens them.

```
// Cursor Chat prompt (Cmd+L):
// Create a ComposeProviders utility component that accepts
// an array of provider components and nests them automatically.
// Use it to wrap ThemeProvider, AuthProvider, and ToastProvider.

import { ReactNode, ComponentType } from 'react';

type Provider = ComponentType<{ children: ReactNode }>;

export function ComposeProviders({
  providers,
  children,
}: {
  providers: Provider[];
  children: ReactNode;
}) {
  return providers.reduceRight(
    (acc, Provider) => <Provider>{acc}</Provider>,
    children
  );
}

// Usage in App.tsx:
// <ComposeProviders providers={[ThemeProvider, AuthProvider, ToastProvider]}>
//   <App />
// </ComposeProviders>
```

**Expected result:** A utility that flattens nested providers into a clean, readable composition.

### 4. Generate selector hooks for granular subscriptions

For contexts with many state values, generate selector hooks that subscribe to specific fields. This prevents components from re-rendering when unrelated state changes.

```
// Cursor Chat prompt (Cmd+L):
// @src/contexts/ThemeContext.tsx Create a useThemeSelector
// hook that accepts a selector function and only re-renders
// when the selected value changes. Use useRef and useSyncExternalStore
// or a simple equality check pattern.

import { useRef, useCallback } from 'react';

export function useThemeSelector<T>(selector: (state: ThemeState) => T): T {
  const state = useContext(ThemeStateContext);
  if (!state) throw new Error('useThemeSelector must be used within ThemeProvider');
  return selector(state);
}

// Usage:
// const theme = useThemeSelector(s => s.theme);
// Only re-renders when theme changes, not when fontSize changes
```

**Expected result:** A selector hook that enables granular subscriptions to context state.

### 5. Test the Context with Cursor-generated tests

Generate tests that verify the context provider, custom hook, and error boundary behavior. Ask Cursor to test that the hook throws outside the provider.

```
// Cursor Chat prompt (Cmd+L):
// @src/contexts/ThemeContext.tsx Generate Vitest tests for
// ThemeContext. Test: 1) Provider renders children,
// 2) useTheme returns correct initial state,
// 3) toggleTheme switches between light and dark,
// 4) useTheme throws when used outside ThemeProvider.
// Use @testing-library/react renderHook.
```

**Expected result:** Tests verifying provider rendering, state management, and error boundary behavior.

## Complete code example

File: `src/contexts/ThemeContext.tsx`

```typescript
import {
  createContext,
  useContext,
  useReducer,
  useMemo,
  useCallback,
  type ReactNode,
  type Dispatch,
} from 'react';

export type Theme = 'light' | 'dark';

export interface ThemeState {
  theme: Theme;
  fontSize: number;
  fontFamily: string;
}

export type ThemeAction =
  | { type: 'TOGGLE_THEME' }
  | { type: 'SET_FONT_SIZE'; payload: number }
  | { type: 'SET_FONT_FAMILY'; payload: string }
  | { type: 'RESET' };

const initialState: ThemeState = {
  theme: 'light',
  fontSize: 16,
  fontFamily: 'Inter',
};

function themeReducer(state: ThemeState, action: ThemeAction): ThemeState {
  switch (action.type) {
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_FONT_SIZE':
      return { ...state, fontSize: Math.max(12, Math.min(24, action.payload)) };
    case 'SET_FONT_FAMILY':
      return { ...state, fontFamily: action.payload };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

const StateCtx = createContext<ThemeState | undefined>(undefined);
const DispatchCtx = createContext<Dispatch<ThemeAction> | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(themeReducer, initialState);
  const memoState = useMemo(() => state, [state]);

  return (
    <StateCtx.Provider value={memoState}>
      <DispatchCtx.Provider value={dispatch}>
        {children}
      </DispatchCtx.Provider>
    </StateCtx.Provider>
  );
}

export function useTheme() {
  const state = useContext(StateCtx);
  const dispatch = useContext(DispatchCtx);
  if (!state || !dispatch) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }

  const toggleTheme = useCallback(() => dispatch({ type: 'TOGGLE_THEME' }), [dispatch]);
  const setFontSize = useCallback((s: number) => dispatch({ type: 'SET_FONT_SIZE', payload: s }), [dispatch]);
  const setFontFamily = useCallback((f: string) => dispatch({ type: 'SET_FONT_FAMILY', payload: f }), [dispatch]);
  const reset = useCallback(() => dispatch({ type: 'RESET' }), [dispatch]);

  return { ...state, toggleTheme, setFontSize, setFontFamily, reset };
}
```

## Common mistakes

- **Creating a single context for both state and dispatch** — Components that only call dispatch will re-render on every state change because the context value includes both. Fix: Split into StateContext and DispatchContext. Add this pattern to your .cursorrules so Cursor does it automatically.
- **Forgetting to memoize the context value** — Creating a new object on every render ({ ...state }) causes all consumers to re-render even if state has not changed. Fix: Add 'Memoize context values with useMemo' to your rules. Cursor will wrap values in useMemo automatically.
- **Exporting the raw context instead of a custom hook** — Raw useContext(MyContext) returns undefined | T, requiring null checks everywhere. A custom hook centralizes the check. Fix: Export only the provider and custom hook. Add 'NEVER export the raw context' to .cursorrules.

## Best practices

- Split state and dispatch into separate contexts to minimize re-renders
- Always create a custom hook that throws if used outside the provider
- Memoize context values with useMemo to prevent unnecessary reference changes
- Use useReducer for contexts with more than two state fields
- Use useCallback for action dispatchers returned from custom hooks
- Export only the provider and custom hook, never the raw context object
- Use ComposeProviders utility to flatten deeply nested provider trees

## Frequently asked questions

### When should I use Context vs Redux?

Use Context for UI state (theme, locale, auth status) that changes infrequently. Use Redux for complex application state with many subscribers, frequent updates, or middleware needs. Add this guidance to .cursorrules so Cursor suggests the right tool.

### Can Cursor generate Context with Zustand instead?

Yes. Zustand is simpler than Context for many use cases. Tell Cursor: 'Use Zustand instead of React Context' in your prompt. Zustand automatically handles re-render optimization.

### How do I test Context providers with Cursor?

Ask Cursor to generate tests using @testing-library/react's renderHook with a wrapper option. Reference the provider component and the custom hook in your prompt.

### Should I use useReducer or useState in Context?

Use useReducer when the context has more than 2 state values or when state transitions are complex. Use useState for simple boolean or single-value contexts.

### How do I persist Context state to localStorage?

Ask Cursor to add a useEffect that saves state to localStorage on changes and initializes from localStorage on mount. Specify 'Add localStorage persistence with a useEffect hook' in your prompt.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-guide-cursor-ai-to-generate-react-context-apis-with-advanced-hook-patterns
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-guide-cursor-ai-to-generate-react-context-apis-with-advanced-hook-patterns
