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.
Prerequisites
- 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
Add Context generation rules to .cursor/rules
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.
1---2description: React Context generation conventions3globs: "src/contexts/**/*.tsx,src/providers/**/*.tsx"4alwaysApply: true5---67## Context Rules8- ALWAYS create a typed context with createContext<Type | undefined>(undefined)9- ALWAYS create a custom hook (useXxxContext) that throws if used outside provider10- Split state and dispatch into separate contexts to prevent re-renders11- Use useReducer for contexts with more than 2 state values12- Memoize context values with useMemo to prevent reference changes13- Place contexts in src/contexts/{name}Context.tsx14- Export the provider component and the custom hook, NEVER the raw contextExpected result: Cursor generates typed, performant Context code following your conventions.
Generate a typed Context with custom hook
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.
1// Cursor Chat prompt (Cmd+L):2// Generate a ThemeContext at src/contexts/ThemeContext.tsx3// State: { theme: 'light' | 'dark', fontSize: number }4// Actions: toggleTheme, setFontSize(size)5// Use useReducer. Split state and dispatch contexts.6// Export ThemeProvider and useTheme hook.78import { createContext, useContext, useReducer, useMemo, ReactNode } from 'react';910type Theme = 'light' | 'dark';11interface ThemeState { theme: Theme; fontSize: number; }12type ThemeAction = 13 | { type: 'TOGGLE_THEME' }14 | { type: 'SET_FONT_SIZE'; payload: number };1516const ThemeStateContext = createContext<ThemeState | undefined>(undefined);17const ThemeDispatchContext = createContext<React.Dispatch<ThemeAction> | undefined>(undefined);1819function themeReducer(state: ThemeState, action: ThemeAction): ThemeState {20 switch (action.type) {21 case 'TOGGLE_THEME':22 return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };23 case 'SET_FONT_SIZE':24 return { ...state, fontSize: action.payload };25 default:26 return state;27 }28}2930export function ThemeProvider({ children }: { children: ReactNode }) {31 const [state, dispatch] = useReducer(themeReducer, { theme: 'light', fontSize: 16 });32 const stateValue = useMemo(() => state, [state]);33 return (34 <ThemeStateContext.Provider value={stateValue}>35 <ThemeDispatchContext.Provider value={dispatch}>36 {children}37 </ThemeDispatchContext.Provider>38 </ThemeStateContext.Provider>39 );40}4142export function useTheme() {43 const state = useContext(ThemeStateContext);44 const dispatch = useContext(ThemeDispatchContext);45 if (!state || !dispatch) throw new Error('useTheme must be used within ThemeProvider');46 return { ...state, toggleTheme: () => dispatch({ type: 'TOGGLE_THEME' }), setFontSize: (size: number) => dispatch({ type: 'SET_FONT_SIZE', payload: size }) };47}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.
Generate a provider composition wrapper
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.
1// Cursor Chat prompt (Cmd+L):2// Create a ComposeProviders utility component that accepts3// an array of provider components and nests them automatically.4// Use it to wrap ThemeProvider, AuthProvider, and ToastProvider.56import { ReactNode, ComponentType } from 'react';78type Provider = ComponentType<{ children: ReactNode }>;910export function ComposeProviders({11 providers,12 children,13}: {14 providers: Provider[];15 children: ReactNode;16}) {17 return providers.reduceRight(18 (acc, Provider) => <Provider>{acc}</Provider>,19 children20 );21}2223// Usage in App.tsx:24// <ComposeProviders providers={[ThemeProvider, AuthProvider, ToastProvider]}>25// <App />26// </ComposeProviders>Expected result: A utility that flattens nested providers into a clean, readable composition.
Generate selector hooks for granular subscriptions
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.
1// Cursor Chat prompt (Cmd+L):2// @src/contexts/ThemeContext.tsx Create a useThemeSelector3// hook that accepts a selector function and only re-renders4// when the selected value changes. Use useRef and useSyncExternalStore5// or a simple equality check pattern.67import { useRef, useCallback } from 'react';89export function useThemeSelector<T>(selector: (state: ThemeState) => T): T {10 const state = useContext(ThemeStateContext);11 if (!state) throw new Error('useThemeSelector must be used within ThemeProvider');12 return selector(state);13}1415// Usage:16// const theme = useThemeSelector(s => s.theme);17// Only re-renders when theme changes, not when fontSize changesExpected result: A selector hook that enables granular subscriptions to context state.
Test the Context with Cursor-generated tests
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.
1// Cursor Chat prompt (Cmd+L):2// @src/contexts/ThemeContext.tsx Generate Vitest tests for3// ThemeContext. Test: 1) Provider renders children,4// 2) useTheme returns correct initial state,5// 3) toggleTheme switches between light and dark,6// 4) useTheme throws when used outside ThemeProvider.7// Use @testing-library/react renderHook.Expected result: Tests verifying provider rendering, state management, and error boundary behavior.
Complete working example
1import {2 createContext,3 useContext,4 useReducer,5 useMemo,6 useCallback,7 type ReactNode,8 type Dispatch,9} from 'react';1011export type Theme = 'light' | 'dark';1213export interface ThemeState {14 theme: Theme;15 fontSize: number;16 fontFamily: string;17}1819export type ThemeAction =20 | { type: 'TOGGLE_THEME' }21 | { type: 'SET_FONT_SIZE'; payload: number }22 | { type: 'SET_FONT_FAMILY'; payload: string }23 | { type: 'RESET' };2425const initialState: ThemeState = {26 theme: 'light',27 fontSize: 16,28 fontFamily: 'Inter',29};3031function themeReducer(state: ThemeState, action: ThemeAction): ThemeState {32 switch (action.type) {33 case 'TOGGLE_THEME':34 return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };35 case 'SET_FONT_SIZE':36 return { ...state, fontSize: Math.max(12, Math.min(24, action.payload)) };37 case 'SET_FONT_FAMILY':38 return { ...state, fontFamily: action.payload };39 case 'RESET':40 return initialState;41 default:42 return state;43 }44}4546const StateCtx = createContext<ThemeState | undefined>(undefined);47const DispatchCtx = createContext<Dispatch<ThemeAction> | undefined>(undefined);4849export function ThemeProvider({ children }: { children: ReactNode }) {50 const [state, dispatch] = useReducer(themeReducer, initialState);51 const memoState = useMemo(() => state, [state]);5253 return (54 <StateCtx.Provider value={memoState}>55 <DispatchCtx.Provider value={dispatch}>56 {children}57 </DispatchCtx.Provider>58 </StateCtx.Provider>59 );60}6162export function useTheme() {63 const state = useContext(StateCtx);64 const dispatch = useContext(DispatchCtx);65 if (!state || !dispatch) {66 throw new Error('useTheme must be used within a ThemeProvider');67 }6869 const toggleTheme = useCallback(() => dispatch({ type: 'TOGGLE_THEME' }), [dispatch]);70 const setFontSize = useCallback((s: number) => dispatch({ type: 'SET_FONT_SIZE', payload: s }), [dispatch]);71 const setFontFamily = useCallback((f: string) => dispatch({ type: 'SET_FONT_FAMILY', payload: f }), [dispatch]);72 const reset = useCallback(() => dispatch({ type: 'RESET' }), [dispatch]);7374 return { ...state, toggleTheme, setFontSize, setFontFamily, reset };75}Common mistakes when generating shared state logic with Cursor
Why it's a problem: Creating a single context for both state and dispatch
How to avoid: Split into StateContext and DispatchContext. Add this pattern to your .cursorrules so Cursor does it automatically.
Why it's a problem: Forgetting to memoize the context value
How to avoid: Add 'Memoize context values with useMemo' to your rules. Cursor will wrap values in useMemo automatically.
Why it's a problem: Exporting the raw context instead of a custom hook
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Create a React ThemeContext with TypeScript that manages theme (light/dark), fontSize, and fontFamily. Use useReducer for state management. Split into separate state and dispatch contexts to prevent unnecessary re-renders. Export a ThemeProvider component and a useTheme custom hook with toggleTheme, setFontSize, setFontFamily, and reset actions.
In Cursor Chat (Cmd+L): @.cursor/rules/context.mdc Generate an AuthContext at src/contexts/AuthContext.tsx. State: { user: User | null, token: string | null, isLoading: boolean }. Actions: login(user, token), logout, setLoading. Use useReducer, split state/dispatch contexts, export AuthProvider and useAuth hook.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation