or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-management.mdcore-data-fetching.mdglobal-configuration.mdimmutable-data.mdindex.mdinfinite-loading.mdmutations.mdsubscriptions.md

infinite-loading.mddocs/

0

# Infinite Loading

1

2

The `useSWRInfinite` hook provides pagination and infinite loading capabilities with automatic page management, size control, and optimized revalidation.

3

4

## Capabilities

5

6

### useSWRInfinite Hook

7

8

Hook for infinite loading and pagination scenarios with automatic page management.

9

10

```typescript { .api }

11

/**

12

* Hook for infinite loading and pagination scenarios

13

* @param getKey - Function that returns the key for each page

14

* @param fetcher - Function that fetches data for each page

15

* @param config - Configuration options extending SWRConfiguration

16

* @returns SWRInfiniteResponse with data array, size control, and loading states

17

*/

18

function useSWRInfinite<Data = any, Error = any>(

19

getKey: SWRInfiniteKeyLoader<Data>,

20

fetcher?: SWRInfiniteFetcher<Data> | null,

21

config?: SWRInfiniteConfiguration<Data, Error>

22

): SWRInfiniteResponse<Data, Error>;

23

24

function unstable_serialize(getKey: SWRInfiniteKeyLoader): string | undefined;

25

```

26

27

**Usage Examples:**

28

29

```typescript

30

import useSWRInfinite from "swr/infinite";

31

32

// Basic infinite loading

33

const getKey = (pageIndex: number, previousPageData: any) => {

34

if (previousPageData && !previousPageData.length) return null; // reached the end

35

return `/api/issues?page=${pageIndex}&limit=10`;

36

};

37

38

const { data, error, size, setSize, isValidating } = useSWRInfinite(

39

getKey,

40

fetcher

41

);

42

43

// Cursor-based pagination

44

const getKey = (pageIndex: number, previousPageData: any) => {

45

if (previousPageData && !previousPageData.nextCursor) return null;

46

if (pageIndex === 0) return "/api/posts?limit=10";

47

return `/api/posts?cursor=${previousPageData.nextCursor}&limit=10`;

48

};

49

50

// Load more functionality

51

const issues = data ? data.flat() : [];

52

const isLoadingMore = data && typeof data[size - 1] === "undefined";

53

const isEmpty = data?.[0]?.length === 0;

54

const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);

55

56

const loadMore = () => setSize(size + 1);

57

```

58

59

### SWR Infinite Response

60

61

The return value from `useSWRInfinite` with specialized properties for pagination.

62

63

```typescript { .api }

64

interface SWRInfiniteResponse<Data, Error> {

65

/** Array of page data (undefined if not loaded) */

66

data: Data[] | undefined;

67

/** Error from any page (undefined if no error) */

68

error: Error | undefined;

69

/** Scoped mutate function for infinite data */

70

mutate: SWRInfiniteKeyedMutator<Data>;

71

/** Current number of pages */

72

size: number;

73

/** Function to change the number of pages */

74

setSize: (size: number | ((size: number) => number)) => Promise<Data[] | undefined>;

75

/** True when any page is validating */

76

isValidating: boolean;

77

/** True when loading the first page for the first time */

78

isLoading: boolean;

79

}

80

```

81

82

### Key Loader Function

83

84

Function that generates the key for each page, enabling different pagination strategies.

85

86

```typescript { .api }

87

type SWRInfiniteKeyLoader<Data = any, Args = any> = (

88

index: number,

89

previousPageData: Data | null

90

) => Args | null;

91

```

92

93

**Key Loader Examples:**

94

95

```typescript

96

// Offset-based pagination

97

const getKey = (pageIndex: number, previousPageData: any) => {

98

return `/api/data?offset=${pageIndex * 10}&limit=10`;

99

};

100

101

// Cursor-based pagination

102

const getKey = (pageIndex: number, previousPageData: any) => {

103

if (previousPageData && !previousPageData.nextCursor) return null;

104

if (pageIndex === 0) return "/api/data?limit=10";

105

return `/api/data?cursor=${previousPageData.nextCursor}&limit=10`;

106

};

107

108

// Page number pagination

109

const getKey = (pageIndex: number, previousPageData: any) => {

110

if (previousPageData && previousPageData.isLastPage) return null;

111

return `/api/data?page=${pageIndex + 1}`;

112

};

113

114

// Conditional loading (stop when empty)

115

const getKey = (pageIndex: number, previousPageData: any) => {

116

if (previousPageData && !previousPageData.length) return null;

117

return `/api/data?page=${pageIndex}`;

118

};

119

120

// Complex key with multiple parameters

121

const getKey = (pageIndex: number, previousPageData: any) => {

122

return ["/api/search", query, pageIndex, filters];

123

};

124

```

125

126

### Configuration Options

127

128

Extended configuration options specific to infinite loading.

129

130

```typescript { .api }

131

interface SWRInfiniteConfiguration<Data = any, Error = any>

132

extends Omit<SWRConfiguration<Data[], Error>, 'fetcher'> {

133

/** Initial number of pages to load (default: 1) */

134

initialSize?: number;

135

/** Revalidate all pages when any page revalidates (default: false) */

136

revalidateAll?: boolean;

137

/** Keep page size when key changes (default: false) */

138

persistSize?: boolean;

139

/** Revalidate first page on mount and focus (default: true) */

140

revalidateFirstPage?: boolean;

141

/** Load pages in parallel instead of sequentially (default: false) */

142

parallel?: boolean;

143

/** Custom comparison function for page data */

144

compare?: SWRInfiniteCompareFn<Data>;

145

/** Fetcher function for infinite data */

146

fetcher?: SWRInfiniteFetcher<Data> | null;

147

}

148

149

type SWRInfiniteFetcher<Data = any, KeyLoader extends SWRInfiniteKeyLoader<any> = SWRInfiniteKeyLoader<Data>> =

150

KeyLoader extends (...args: any[]) => infer Arg | null

151

? Arg extends null

152

? never

153

: (...args: [Arg]) => Data | Promise<Data>

154

: never;

155

156

interface SWRInfiniteCompareFn<Data> {

157

(a: Data | undefined, b: Data | undefined): boolean;

158

}

159

```

160

161

**Configuration Examples:**

162

163

```typescript

164

// Load 3 pages initially

165

const { data } = useSWRInfinite(getKey, fetcher, {

166

initialSize: 3

167

});

168

169

// Parallel page loading

170

const { data } = useSWRInfinite(getKey, fetcher, {

171

parallel: true

172

});

173

174

// Revalidate all pages on focus

175

const { data } = useSWRInfinite(getKey, fetcher, {

176

revalidateAll: true,

177

revalidateOnFocus: true

178

});

179

180

// Persist page size across key changes

181

const { data } = useSWRInfinite(getKey, fetcher, {

182

persistSize: true

183

});

184

185

// Custom page comparison

186

const { data } = useSWRInfinite(getKey, fetcher, {

187

compare: (a, b) => JSON.stringify(a) === JSON.stringify(b)

188

});

189

```

190

191

### Size Management

192

193

Control the number of pages loaded with the `setSize` function.

194

195

```typescript { .api }

196

setSize: (size: number | ((size: number) => number)) => Promise<Data[] | undefined>;

197

```

198

199

**Size Management Examples:**

200

201

```typescript

202

const { data, size, setSize } = useSWRInfinite(getKey, fetcher);

203

204

// Load more pages

205

const loadMore = () => setSize(size + 1);

206

207

// Load specific number of pages

208

const loadExact = (pageCount: number) => setSize(pageCount);

209

210

// Function-based size update

211

const doublePages = () => setSize(current => current * 2);

212

213

// Reset to first page

214

const reset = () => setSize(1);

215

216

// Load all available pages (be careful!)

217

const loadAll = () => {

218

// Keep loading until no more data

219

let currentSize = size;

220

const loadNext = () => {

221

setSize(currentSize + 1).then((newData) => {

222

if (newData && newData[currentSize]?.length > 0) {

223

currentSize++;

224

loadNext();

225

}

226

});

227

};

228

loadNext();

229

};

230

```

231

232

### Infinite Mutate Function

233

234

Specialized mutate function for infinite data structures.

235

236

```typescript { .api }

237

interface SWRInfiniteKeyedMutator<Data> {

238

/**

239

* Mutate infinite data with support for page-level updates

240

* @param data - New data array, Promise, or function returning new data

241

* @param options - Mutation options including revalidation control

242

* @returns Promise resolving to the new data array

243

*/

244

(

245

data?: Data[] | Promise<Data[]> | ((currentData: Data[] | undefined) => Data[] | undefined),

246

options?: boolean | SWRInfiniteMutatorOptions<Data>

247

): Promise<Data[] | undefined>;

248

}

249

250

interface SWRInfiniteMutatorOptions<Data = any> {

251

/** Whether to revalidate after mutation (default: true) */

252

revalidate?: boolean;

253

/** Control which pages to revalidate */

254

revalidate?: boolean | ((pageData: Data, pageArg: any) => boolean);

255

}

256

```

257

258

**Infinite Mutate Examples:**

259

260

```typescript

261

const { data, mutate } = useSWRInfinite(getKey, fetcher);

262

263

// Update all pages

264

await mutate([...newPagesData]);

265

266

// Add new item to first page

267

await mutate((currentData) => {

268

if (!currentData) return undefined;

269

return [

270

[newItem, ...currentData[0]],

271

...currentData.slice(1)

272

];

273

});

274

275

// Remove item from all pages

276

await mutate((currentData) => {

277

if (!currentData) return undefined;

278

return currentData.map(page =>

279

page.filter(item => item.id !== removedItemId)

280

);

281

});

282

283

// Optimistic update for new page

284

await mutate(

285

[...(data || []), optimisticNewPage],

286

{ revalidate: false }

287

);

288

```

289

290

### Advanced Patterns

291

292

Common patterns for complex infinite loading scenarios.

293

294

**Load More Button:**

295

296

```typescript

297

function InfiniteList() {

298

const { data, error, size, setSize, isValidating } = useSWRInfinite(

299

getKey,

300

fetcher

301

);

302

303

const issues = data ? data.flat() : [];

304

const isLoadingMore = data && typeof data[size - 1] === "undefined";

305

const isEmpty = data?.[0]?.length === 0;

306

const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);

307

308

return (

309

<div>

310

{issues.map(issue => (

311

<div key={issue.id}>{issue.title}</div>

312

))}

313

314

{error && <div>Error: {error.message}</div>}

315

316

<button

317

disabled={isLoadingMore || isReachingEnd}

318

onClick={() => setSize(size + 1)}

319

>

320

{isLoadingMore ? "Loading..." :

321

isReachingEnd ? "No more data" : "Load more"}

322

</button>

323

</div>

324

);

325

}

326

```

327

328

**Infinite Scroll:**

329

330

```typescript

331

function InfiniteScroll() {

332

const { data, setSize, size } = useSWRInfinite(getKey, fetcher);

333

const [isIntersecting, setIsIntersecting] = useState(false);

334

const loadMoreRef = useRef(null);

335

336

useEffect(() => {

337

const observer = new IntersectionObserver(

338

([entry]) => setIsIntersecting(entry.isIntersecting),

339

{ threshold: 1 }

340

);

341

342

if (loadMoreRef.current) {

343

observer.observe(loadMoreRef.current);

344

}

345

346

return () => observer.disconnect();

347

}, []);

348

349

useEffect(() => {

350

if (isIntersecting) {

351

setSize(size + 1);

352

}

353

}, [isIntersecting, setSize, size]);

354

355

const items = data ? data.flat() : [];

356

357

return (

358

<div>

359

{items.map(item => (

360

<div key={item.id}>{item.title}</div>

361

))}

362

<div ref={loadMoreRef} />

363

</div>

364

);

365

}

366

```

367

368

**Search with Infinite Results:**

369

370

```typescript

371

function InfiniteSearch() {

372

const [query, setQuery] = useState("");

373

374

const getKey = (pageIndex: number, previousPageData: any) => {

375

if (!query) return null;

376

if (previousPageData && !previousPageData.length) return null;

377

return `/api/search?q=${query}&page=${pageIndex}`;

378

};

379

380

const { data, setSize, size } = useSWRInfinite(getKey, fetcher);

381

382

// Reset pages when query changes

383

useEffect(() => {

384

setSize(1);

385

}, [query, setSize]);

386

387

const results = data ? data.flat() : [];

388

389

return (

390

<div>

391

<input

392

value={query}

393

onChange={(e) => setQuery(e.target.value)}

394

placeholder="Search..."

395

/>

396

397

{results.map(result => (

398

<div key={result.id}>{result.title}</div>

399

))}

400

401

{query && (

402

<button onClick={() => setSize(size + 1)}>

403

Load more results

404

</button>

405

)}

406

</div>

407

);

408

}

409

```