or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdreact-hooks.mdtypes-utilities.mdvirtualizer-engine.md

react-hooks.mddocs/

0

# React Virtualization Hooks

1

2

React-specific hooks that provide virtualized scrolling for elements and windows with automatic state management and optimal re-rendering performance.

3

4

## Capabilities

5

6

### useVirtualizer Hook

7

8

Creates a virtualizer instance for element-based scrolling with automatic React integration and state management.

9

10

```typescript { .api }

11

/**

12

* React hook for element-based virtualization

13

* @param options - Virtualization configuration with element-specific defaults

14

* @returns Virtualizer instance with React integration

15

*/

16

export function useVirtualizer<TScrollElement extends Element, TItemElement extends Element>(

17

options: PartialKeys<

18

VirtualizerOptions<TScrollElement, TItemElement>,

19

'observeElementRect' | 'observeElementOffset' | 'scrollToFn'

20

>

21

): Virtualizer<TScrollElement, TItemElement>;

22

```

23

24

The hook automatically provides default implementations for:

25

- `observeElementRect`: Uses ResizeObserver for element size changes

26

- `observeElementOffset`: Monitors scroll position changes

27

- `scrollToFn`: Handles scrolling with element.scrollTo()

28

29

**Usage Examples:**

30

31

```typescript

32

import React from 'react';

33

import { useVirtualizer } from '@tanstack/react-virtual';

34

35

// Basic list virtualization

36

function BasicList({ items }: { items: string[] }) {

37

const parentRef = React.useRef<HTMLDivElement>(null);

38

39

const virtualizer = useVirtualizer({

40

count: items.length,

41

getScrollElement: () => parentRef.current,

42

estimateSize: () => 35,

43

});

44

45

return (

46

<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>

47

<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>

48

{virtualizer.getVirtualItems().map((virtualItem) => (

49

<div

50

key={virtualItem.key}

51

data-index={virtualItem.index}

52

style={{

53

position: 'absolute',

54

top: 0,

55

left: 0,

56

width: '100%',

57

height: `${virtualItem.size}px`,

58

transform: `translateY(${virtualItem.start}px)`,

59

}}

60

>

61

{items[virtualItem.index]}

62

</div>

63

))}

64

</div>

65

</div>

66

);

67

}

68

69

// Horizontal scrolling

70

function HorizontalList({ items }: { items: any[] }) {

71

const parentRef = React.useRef<HTMLDivElement>(null);

72

73

const virtualizer = useVirtualizer({

74

count: items.length,

75

getScrollElement: () => parentRef.current,

76

estimateSize: () => 120,

77

horizontal: true,

78

});

79

80

return (

81

<div ref={parentRef} style={{ width: '400px', overflowX: 'auto' }}>

82

<div style={{ width: `${virtualizer.getTotalSize()}px`, height: '100px', position: 'relative' }}>

83

{virtualizer.getVirtualItems().map((virtualItem) => (

84

<div

85

key={virtualItem.key}

86

data-index={virtualItem.index}

87

style={{

88

position: 'absolute',

89

top: 0,

90

left: 0,

91

height: '100%',

92

width: `${virtualItem.size}px`,

93

transform: `translateX(${virtualItem.start}px)`,

94

}}

95

>

96

Item {virtualItem.index}

97

</div>

98

))}

99

</div>

100

</div>

101

);

102

}

103

104

// Dynamic sizing with measurement

105

function DynamicList({ items }: { items: { content: string; height?: number }[] }) {

106

const parentRef = React.useRef<HTMLDivElement>(null);

107

108

const virtualizer = useVirtualizer({

109

count: items.length,

110

getScrollElement: () => parentRef.current,

111

estimateSize: (index) => items[index]?.height ?? 50,

112

overscan: 3,

113

});

114

115

return (

116

<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>

117

<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>

118

{virtualizer.getVirtualItems().map((virtualItem) => (

119

<div

120

key={virtualItem.key}

121

data-index={virtualItem.index}

122

ref={(node) => virtualizer.measureElement(node)}

123

style={{

124

position: 'absolute',

125

top: 0,

126

left: 0,

127

width: '100%',

128

transform: `translateY(${virtualItem.start}px)`,

129

}}

130

>

131

<div style={{ padding: '8px' }}>

132

{items[virtualItem.index]?.content}

133

</div>

134

</div>

135

))}

136

</div>

137

</div>

138

);

139

}

140

```

141

142

### useWindowVirtualizer Hook

143

144

Creates a virtualizer instance for window-based scrolling, ideal for full-page virtualization scenarios.

145

146

```typescript { .api }

147

/**

148

* React hook for window-based virtualization

149

* @param options - Virtualization configuration with window-specific defaults

150

* @returns Virtualizer instance configured for window scrolling

151

*/

152

export function useWindowVirtualizer<TItemElement extends Element>(

153

options: PartialKeys<

154

VirtualizerOptions<Window, TItemElement>,

155

| 'getScrollElement'

156

| 'observeElementRect'

157

| 'observeElementOffset'

158

| 'scrollToFn'

159

>

160

): Virtualizer<Window, TItemElement>;

161

```

162

163

The hook automatically provides default implementations for:

164

- `getScrollElement`: Returns the window object

165

- `observeElementRect`: Uses window resize events

166

- `observeElementOffset`: Monitors window scroll position

167

- `scrollToFn`: Handles scrolling with window.scrollTo()

168

- `initialOffset`: Uses current window.scrollY position

169

170

**Usage Examples:**

171

172

```typescript

173

import React from 'react';

174

import { useWindowVirtualizer } from '@tanstack/react-virtual';

175

176

// Full-page virtualization

177

function WindowVirtualizedList({ items }: { items: string[] }) {

178

const virtualizer = useWindowVirtualizer({

179

count: items.length,

180

estimateSize: () => 100,

181

overscan: 5,

182

});

183

184

return (

185

<div style={{ paddingTop: '200px' }}>

186

<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>

187

{virtualizer.getVirtualItems().map((virtualItem) => (

188

<div

189

key={virtualItem.key}

190

data-index={virtualItem.index}

191

style={{

192

position: 'absolute',

193

top: 0,

194

left: 0,

195

width: '100%',

196

height: `${virtualItem.size}px`,

197

transform: `translateY(${virtualItem.start}px)`,

198

}}

199

>

200

<div style={{ padding: '16px', border: '1px solid #ccc' }}>

201

{items[virtualItem.index]}

202

</div>

203

</div>

204

))}

205

</div>

206

</div>

207

);

208

}

209

210

// Infinite scroll with window virtualization

211

function InfiniteScrollList() {

212

const [items, setItems] = React.useState<string[]>(

213

Array.from({ length: 100 }, (_, i) => `Item ${i}`)

214

);

215

const [isLoading, setIsLoading] = React.useState(false);

216

217

const virtualizer = useWindowVirtualizer({

218

count: items.length,

219

estimateSize: () => 80,

220

overscan: 10,

221

});

222

223

const virtualItems = virtualizer.getVirtualItems();

224

const lastItem = virtualItems[virtualItems.length - 1];

225

226

React.useEffect(() => {

227

if (!lastItem || isLoading) return;

228

229

if (lastItem.index >= items.length - 1) {

230

setIsLoading(true);

231

// Simulate loading more items

232

setTimeout(() => {

233

setItems(prev => [

234

...prev,

235

...Array.from({ length: 50 }, (_, i) => `Item ${prev.length + i}`)

236

]);

237

setIsLoading(false);

238

}, 1000);

239

}

240

}, [lastItem?.index, items.length, isLoading]);

241

242

return (

243

<div>

244

<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>

245

{virtualItems.map((virtualItem) => (

246

<div

247

key={virtualItem.key}

248

data-index={virtualItem.index}

249

style={{

250

position: 'absolute',

251

top: 0,

252

left: 0,

253

width: '100%',

254

height: `${virtualItem.size}px`,

255

transform: `translateY(${virtualItem.start}px)`,

256

padding: '8px',

257

borderBottom: '1px solid #eee',

258

}}

259

>

260

{items[virtualItem.index]}

261

</div>

262

))}

263

</div>

264

{isLoading && <div style={{ padding: '20px', textAlign: 'center' }}>Loading...</div>}

265

</div>

266

);

267

}

268

```

269

270

## Configuration Options

271

272

Both hooks accept the same base `VirtualizerOptions` with certain defaults provided:

273

274

### Required Options

275

276

```typescript { .api }

277

export interface RequiredVirtualizerOptions {

278

/** Total number of items to virtualize */

279

count: number;

280

/** Function to estimate the size of each item */

281

estimateSize: (index: number) => number;

282

}

283

```

284

285

### Common Optional Options

286

287

```typescript { .api }

288

export interface CommonVirtualizerOptions {

289

/** Enable horizontal scrolling instead of vertical */

290

horizontal?: boolean;

291

/** Number of items to render outside the visible area */

292

overscan?: number;

293

/** Padding at the start of the scroll area */

294

paddingStart?: number;

295

/** Padding at the end of the scroll area */

296

paddingEnd?: number;

297

/** Number of lanes for grid layout */

298

lanes?: number;

299

/** Gap between items in pixels */

300

gap?: number;

301

/** Enable debug logging */

302

debug?: boolean;

303

/** Enable right-to-left layout */

304

isRtl?: boolean;

305

/** Custom function to extract keys from indices */

306

getItemKey?: (index: number) => Key;

307

/** Custom function to extract visible range */

308

rangeExtractor?: (range: Range) => Array<number>;

309

/** Custom element measurement function */

310

measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;

311

}

312

```

313

314

## Performance Considerations

315

316

### Optimal Re-rendering

317

318

Both hooks use React's state management to trigger re-renders only when necessary:

319

320

- Automatic batching of scroll updates using `flushSync` for smooth scrolling

321

- Memoized calculations to prevent unnecessary recalculations

322

- Efficient range calculation to minimize DOM operations

323

324

### Memory Management

325

326

- Virtual items are created and destroyed as needed

327

- Element references are automatically cleaned up

328

- ResizeObserver connections are managed automatically

329

330

### Best Practices

331

332

1. **Use stable estimateSize functions**: Avoid creating new functions on every render

333

2. **Implement proper key extraction**: Use `getItemKey` for items with stable identifiers

334

3. **Optimize item rendering**: Use React.memo for item components when appropriate

335

4. **Handle dynamic content**: Use `measureElement` ref callback for accurate sizing of dynamic content