Skip to main content
RapidDev - Software Development Agency
cursor-tutorial

How to generate data fetching code with Cursor

Cursor can generate type-safe SWR data fetching hooks when you provide your API types and fetcher patterns as context. This tutorial shows how to set up .cursorrules for consistent SWR patterns, reference your API types with @file, and prompt Cursor to generate reusable hooks with proper error handling, revalidation, and TypeScript generics.

What you'll learn

  • How to configure .cursorrules for consistent SWR hook generation
  • How to prompt Cursor to create typed data fetching hooks
  • How to generate a reusable fetcher and error handler
  • How to extend generated hooks with mutation and revalidation
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read10-15 minCursor Free+, React projects with SWRMarch 2026RapidDev Engineering Team
TL;DR

Cursor can generate type-safe SWR data fetching hooks when you provide your API types and fetcher patterns as context. This tutorial shows how to set up .cursorrules for consistent SWR patterns, reference your API types with @file, and prompt Cursor to generate reusable hooks with proper error handling, revalidation, and TypeScript generics.

Generating data fetching code with Cursor and SWR

SWR by Vercel provides React hooks for data fetching with caching, revalidation, and optimistic updates. Cursor can generate these hooks quickly, but without context about your API types and fetcher pattern, the output lacks type safety and proper error handling. This tutorial establishes a pattern that produces production-ready SWR hooks every time.

Prerequisites

  • Cursor installed with a React + TypeScript project
  • SWR installed: npm install swr
  • API endpoints to fetch data from
  • Familiarity with React hooks and Cursor Chat (Cmd+L)

Step-by-step guide

1

Create a base fetcher module

Generate a typed fetcher function that all SWR hooks will share. This centralizes error handling, authentication headers, and base URL configuration. Use Cursor Chat (Cmd+L) to generate it.

src/lib/fetcher.ts
1// Cursor Chat prompt (Cmd+L):
2// Create a typed fetcher function for SWR at
3// src/lib/fetcher.ts. It should: use the API_BASE_URL
4// from environment, include auth token from localStorage,
5// throw typed errors with status code and message,
6// handle JSON parsing, and be generic over the response type.
7
8import { config } from '@/config/env';
9
10export class FetchError extends Error {
11 constructor(public status: number, message: string) {
12 super(message);
13 this.name = 'FetchError';
14 }
15}
16
17export async function fetcher<T>(url: string): Promise<T> {
18 const token = localStorage.getItem('auth_token');
19 const res = await fetch(`${config.apiBaseUrl}${url}`, {
20 headers: {
21 'Content-Type': 'application/json',
22 ...(token ? { Authorization: `Bearer ${token}` } : {}),
23 },
24 });
25 if (!res.ok) {
26 throw new FetchError(res.status, await res.text());
27 }
28 return res.json();
29}

Expected result: A reusable, typed fetcher function that handles auth, errors, and JSON parsing.

2

Add SWR rules to .cursor/rules

Create rules that enforce consistent SWR patterns across all generated hooks. This prevents Cursor from generating inline fetchers or missing error states.

.cursor/rules/swr.mdc
1---
2description: SWR data fetching conventions
3globs: "src/hooks/**/*.ts,src/hooks/**/*.tsx"
4alwaysApply: true
5---
6
7## SWR Hook Rules
8- ALWAYS import fetcher from @/lib/fetcher
9- ALWAYS type the return value with SWR's generic: useSWR<Type>
10- ALWAYS handle loading, error, and data states in returned object
11- Name hooks as use{Resource}: useUsers, useOrder, useProducts
12- Place all hooks in src/hooks/
13- Use SWR keys as API paths: '/users', '/orders/123'
14- Include mutate function in return for cache invalidation
15- NEVER create inline fetch calls inside components

Expected result: Cursor follows consistent SWR conventions when generating data fetching hooks.

3

Generate a data fetching hook

Ask Cursor to generate a hook for a specific resource. Reference the fetcher and your API types so the output is fully typed.

src/hooks/useUser.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/lib/fetcher.ts @src/types/user.ts
3// Generate a useUser(id) hook using SWR that:
4// - Fetches from /users/:id
5// - Returns { user, isLoading, isError, mutate }
6// - Only fetches when id is provided
7// - Uses our typed fetcher
8
9import useSWR from 'swr';
10import { fetcher } from '@/lib/fetcher';
11import type { User } from '@/types/user';
12
13export function useUser(id: string | undefined) {
14 const { data, error, isLoading, mutate } = useSWR<User>(
15 id ? `/users/${id}` : null,
16 fetcher
17 );
18
19 return {
20 user: data,
21 isLoading,
22 isError: !!error,
23 error,
24 mutate,
25 };
26}

Pro tip: Pass null as the SWR key to conditionally skip fetching. Cursor understands this pattern when you say 'only fetch when id is provided.'

Expected result: A typed SWR hook with conditional fetching, error state, and cache mutation.

4

Generate a paginated list hook

For list endpoints, generate a hook that handles pagination with SWR. Reference your pagination types and the fetcher module.

src/hooks/useUsers.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/lib/fetcher.ts @src/types/api.ts
3// Generate a useUsers hook that fetches a paginated list
4// from /users?page=N&limit=N. Return users array, total
5// count, loading state, error, and a setPage function.
6// Use SWR with the page number in the key.
7
8import useSWR from 'swr';
9import { useState } from 'react';
10import { fetcher } from '@/lib/fetcher';
11import type { PaginatedResponse } from '@/types/api';
12import type { User } from '@/types/user';
13
14export function useUsers(limit = 20) {
15 const [page, setPage] = useState(1);
16 const { data, error, isLoading, mutate } = useSWR<PaginatedResponse<User>>(
17 `/users?page=${page}&limit=${limit}`,
18 fetcher
19 );
20
21 return {
22 users: data?.items ?? [],
23 total: data?.total ?? 0,
24 page,
25 setPage,
26 isLoading,
27 isError: !!error,
28 mutate,
29 };
30}

Expected result: A paginated SWR hook with page state management and typed response handling.

5

Generate a mutation helper alongside the hook

SWR hooks are read-only by default. Ask Cursor to generate a companion mutation function that updates the server and revalidates the SWR cache. This completes the data fetching pattern.

src/hooks/useUpdateUser.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/hooks/useUser.ts @src/lib/fetcher.ts
3// Add a useUpdateUser hook that:
4// - Takes userId
5// - Returns an updateUser(data) async function
6// - Sends PUT /users/:id with the data
7// - Calls mutate() from useUser to revalidate the cache
8// - Uses optimistic updates
9
10import useSWRMutation from 'swr/mutation';
11import type { User } from '@/types/user';
12import { config } from '@/config/env';
13
14async function updateUserFn(
15 url: string,
16 { arg }: { arg: Partial<User> }
17) {
18 const res = await fetch(`${config.apiBaseUrl}${url}`, {
19 method: 'PUT',
20 headers: { 'Content-Type': 'application/json' },
21 body: JSON.stringify(arg),
22 });
23 if (!res.ok) throw new Error('Update failed');
24 return res.json();
25}
26
27export function useUpdateUser(id: string) {
28 return useSWRMutation(`/users/${id}`, updateUserFn);
29}

Expected result: A mutation hook that updates the server and revalidates the SWR cache with optimistic updates.

Complete working example

src/lib/fetcher.ts
1/**
2 * Shared SWR fetcher with authentication and error handling.
3 * All useXxx() hooks import this fetcher for consistent behavior.
4 */
5
6import { config } from '@/config/env';
7
8export class FetchError extends Error {
9 public status: number;
10 public info: unknown;
11
12 constructor(status: number, message: string, info?: unknown) {
13 super(message);
14 this.name = 'FetchError';
15 this.status = status;
16 this.info = info;
17 }
18}
19
20export async function fetcher<T>(url: string): Promise<T> {
21 const token =
22 typeof window !== 'undefined'
23 ? localStorage.getItem('auth_token')
24 : null;
25
26 const res = await fetch(`${config.apiBaseUrl}${url}`, {
27 headers: {
28 'Content-Type': 'application/json',
29 ...(token ? { Authorization: `Bearer ${token}` } : {}),
30 },
31 });
32
33 if (!res.ok) {
34 const info = await res.json().catch(() => null);
35 throw new FetchError(
36 res.status,
37 info?.message || `Request failed: ${res.status}`,
38 info
39 );
40 }
41
42 return res.json() as Promise<T>;
43}
44
45export async function mutationFetcher<T>(
46 url: string,
47 { arg }: { arg: { method: string; body?: unknown } }
48): Promise<T> {
49 const token = localStorage.getItem('auth_token');
50 const res = await fetch(`${config.apiBaseUrl}${url}`, {
51 method: arg.method,
52 headers: {
53 'Content-Type': 'application/json',
54 ...(token ? { Authorization: `Bearer ${token}` } : {}),
55 },
56 body: arg.body ? JSON.stringify(arg.body) : undefined,
57 });
58
59 if (!res.ok) {
60 const info = await res.json().catch(() => null);
61 throw new FetchError(res.status, info?.message || 'Mutation failed', info);
62 }
63
64 return res.json() as Promise<T>;
65}

Common mistakes when generating data fetching code with Cursor

Why it's a problem: Creating inline fetch calls inside components instead of reusable hooks

How to avoid: Add 'NEVER create inline fetch calls inside components' to .cursorrules and always ask for 'a custom hook using SWR.'

Why it's a problem: Forgetting conditional fetching with null keys

How to avoid: In your prompt, specify 'only fetch when id is provided' and Cursor will use the null key pattern.

Why it's a problem: Not typing the SWR generic parameter

How to avoid: Always specify the expected type: useSWR<User>('/users/1', fetcher). Add this to your SWR rules.

Best practices

  • Create a shared fetcher module that all SWR hooks import for consistent auth and error handling
  • Add SWR rules to .cursor/rules mandating typed hooks, shared fetchers, and proper error states
  • Use the null key pattern for conditional fetching when parameters may be undefined
  • Always type SWR's generic parameter (useSWR<Type>) for full type safety
  • Generate mutation hooks alongside read hooks for complete CRUD coverage
  • Place all hooks in src/hooks/ with consistent naming: use{Resource}
  • Reference @src/lib/fetcher.ts and @src/types/ in every SWR hook generation prompt

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Create a React data fetching pattern using SWR. Generate: 1) A shared fetcher function with auth headers and typed errors. 2) A useUser(id) hook that conditionally fetches from /users/:id. 3) A useUsers(page, limit) hook with pagination. 4) A useUpdateUser mutation hook. All hooks must be fully typed with TypeScript generics.

Cursor Prompt

In Cursor Chat (Cmd+L): @src/lib/fetcher.ts @src/types/user.ts Generate a useUser(id) SWR hook in src/hooks/useUser.ts. Use our fetcher module. Type the response as User. Return { user, isLoading, isError, mutate }. Only fetch when id is provided. Follow the SWR conventions in our .cursorrules.

Frequently asked questions

Should I use SWR or React Query with Cursor?

Cursor generates good code for both. SWR is simpler with fewer concepts. React Query (TanStack Query) offers more features like query invalidation patterns and devtools. Specify your choice in .cursorrules so Cursor does not mix them.

How do I handle authentication token refresh with SWR hooks?

Add token refresh logic to the shared fetcher: if a 401 is received, refresh the token and retry the request. Add this requirement to your Cursor prompt when generating the fetcher.

Can Cursor generate SWR hooks with real-time subscriptions?

Yes. Ask Cursor to use SWR's refreshInterval option for polling, or combine SWR with a WebSocket connection for push-based updates. Specify the approach in your prompt.

How many SWR hooks should I have per component?

Keep it to 1-3 SWR hooks per component. If a component needs more data sources, create a composite hook that combines multiple SWR calls into a single return object.

Will Cursor handle SWR's cache properly across page navigations?

SWR caching is automatic. Cursor-generated hooks benefit from SWR's cache without extra configuration. For cross-page cache sharing, ensure your SWR keys are consistent. Add this to your .cursorrules.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.