0
# Utility Functions
1
2
Helper functions and utilities for customizing virtualization behavior and enhancing performance.
3
4
## Capabilities
5
6
### Overscan Index Getters
7
8
Functions that calculate which additional (non-visible) items to render for smooth scrolling.
9
10
```javascript { .api }
11
/**
12
* Default overscan calculation for standard usage
13
* @param params - Overscan calculation parameters
14
*/
15
function defaultOverscanIndicesGetter(params: {
16
/** Total number of cells */
17
cellCount: number,
18
/** Number of extra cells to render */
19
overscanCellsCount: number,
20
/** Scroll direction (-1 for backward, 1 for forward) */
21
scrollDirection: number,
22
/** Index of first visible cell */
23
startIndex: number,
24
/** Index of last visible cell */
25
stopIndex: number
26
}): {
27
/** Index of first cell to render (including overscan) */
28
overscanStartIndex: number,
29
/** Index of last cell to render (including overscan) */
30
overscanStopIndex: number
31
};
32
33
/**
34
* Enhanced overscan calculation for accessibility
35
* Renders more cells to improve screen reader performance
36
* @param params - Overscan calculation parameters
37
*/
38
function accessibilityOverscanIndicesGetter(params: {
39
cellCount: number,
40
overscanCellsCount: number,
41
/** Scroll direction (-1 for backward, 1 for forward) */
42
scrollDirection: number,
43
startIndex: number,
44
stopIndex: number
45
}): {
46
overscanStartIndex: number,
47
overscanStopIndex: number
48
};
49
```
50
51
**Usage Examples:**
52
53
```javascript
54
import React from 'react';
55
import {
56
List,
57
Grid,
58
defaultOverscanIndicesGetter,
59
accessibilityOverscanIndicesGetter
60
} from 'react-virtualized';
61
62
// List with custom overscan calculation
63
function CustomOverscanList({ items, highPerformanceMode }) {
64
// Custom overscan getter that adjusts based on performance mode
65
const customOverscanGetter = (params) => {
66
if (highPerformanceMode) {
67
// Render fewer extra items for better performance
68
return defaultOverscanIndicesGetter({
69
...params,
70
overscanCellsCount: Math.min(params.overscanCellsCount, 2)
71
});
72
} else {
73
// Use accessibility mode for better UX
74
return accessibilityOverscanIndicesGetter(params);
75
}
76
};
77
78
const rowRenderer = ({ index, key, style }) => (
79
<div key={key} style={style} className="list-item">
80
{items[index]}
81
</div>
82
);
83
84
return (
85
<div>
86
<div className="performance-indicator">
87
Mode: {highPerformanceMode ? 'High Performance' : 'Accessibility'}
88
</div>
89
<List
90
height={400}
91
width={300}
92
rowCount={items.length}
93
rowHeight={50}
94
rowRenderer={rowRenderer}
95
overscanIndicesGetter={customOverscanGetter}
96
overscanRowCount={5}
97
/>
98
</div>
99
);
100
}
101
102
// Grid with accessibility-focused overscan
103
function AccessibleGrid({ data }) {
104
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => (
105
<div key={key} style={style} className="grid-cell">
106
{data[rowIndex][columnIndex]}
107
</div>
108
);
109
110
return (
111
<Grid
112
cellRenderer={cellRenderer}
113
columnCount={data[0].length}
114
columnWidth={100}
115
height={400}
116
rowCount={data.length}
117
rowHeight={50}
118
width={500}
119
overscanIndicesGetter={accessibilityOverscanIndicesGetter}
120
overscanColumnCount={3}
121
overscanRowCount={3}
122
/>
123
);
124
}
125
```
126
127
### Cell Range Renderer
128
129
Controls how cells are rendered within the visible and overscan ranges.
130
131
```javascript { .api }
132
/**
133
* Default cell range renderer
134
* @param params - Cell range rendering parameters
135
*/
136
function defaultCellRangeRenderer(params: {
137
/** Cache of rendered cells */
138
cellCache: object,
139
/** Cell rendering function */
140
cellRenderer: (params: {columnIndex: number, key: string, rowIndex: number, style: object}) => React.Node,
141
/** First visible column index */
142
columnStartIndex: number,
143
/** Last visible column index */
144
columnStopIndex: number,
145
/** Deferred measurement cache */
146
deferredMeasurementCache?: object,
147
/** Horizontal scroll adjustment */
148
horizontalOffsetAdjustment: number,
149
/** Whether scrolling is in progress */
150
isScrolling: boolean,
151
/** Parent component reference */
152
parent: object,
153
/** First visible row index */
154
rowStartIndex: number,
155
/** Last visible row index */
156
rowStopIndex: number,
157
/** Cache of computed styles */
158
styleCache: object,
159
/** Vertical scroll adjustment */
160
verticalOffsetAdjustment: number,
161
/** Map of visible column indices */
162
visibleColumnIndices: object,
163
/** Map of visible row indices */
164
visibleRowIndices: object
165
}): React.Node[];
166
```
167
168
**Usage Examples:**
169
170
```javascript
171
import React from 'react';
172
import { Grid, defaultCellRangeRenderer } from 'react-virtualized';
173
174
// Grid with custom cell range renderer for performance optimization
175
function OptimizedGrid({ data, enableDebugMode }) {
176
const customCellRangeRenderer = (params) => {
177
if (enableDebugMode) {
178
// Add debug information to rendered cells
179
console.log('Rendering cells:', {
180
columns: `${params.columnStartIndex}-${params.columnStopIndex}`,
181
rows: `${params.rowStartIndex}-${params.rowStopIndex}`,
182
isScrolling: params.isScrolling
183
});
184
}
185
186
// Use default renderer but with custom caching strategy
187
const cells = defaultCellRangeRenderer(params);
188
189
if (enableDebugMode) {
190
// Add debug borders to cells during scrolling
191
return cells.map(cell =>
192
React.cloneElement(cell, {
193
...cell.props,
194
style: {
195
...cell.props.style,
196
border: params.isScrolling ? '1px solid red' : '1px solid #ddd'
197
}
198
})
199
);
200
}
201
202
return cells;
203
};
204
205
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => (
206
<div key={key} style={style} className="grid-cell">
207
{data[rowIndex][columnIndex]}
208
</div>
209
);
210
211
return (
212
<Grid
213
cellRenderer={cellRenderer}
214
cellRangeRenderer={customCellRangeRenderer}
215
columnCount={data[0].length}
216
columnWidth={120}
217
height={400}
218
rowCount={data.length}
219
rowHeight={50}
220
width={600}
221
/>
222
);
223
}
224
```
225
226
### Multi-Column Sort Utility
227
228
Advanced sorting utility for tables with multiple column sorting capabilities.
229
230
```javascript { .api }
231
/**
232
* Multi-column sort utility for tables
233
* @param sortFunction - Function to handle sort events
234
* @param options - Configuration options
235
*/
236
function createTableMultiSort(
237
sortFunction: (params: {sortBy: string, sortDirection: string}) => void,
238
options?: {
239
/** Default columns to sort by */
240
defaultSortBy?: Array<string>,
241
/** Default sort directions for columns */
242
defaultSortDirection?: {[key: string]: string}
243
}
244
): (params: {event: Event, sortBy: string, sortDirection: string}) => void;
245
```
246
247
**Usage Examples:**
248
249
```javascript
250
import React, { useState } from 'react';
251
import { Table, Column, createTableMultiSort, SortDirection } from 'react-virtualized';
252
253
// Advanced table with multi-column sorting
254
function MultiSortTable({ employees }) {
255
const [sortState, setSortState] = useState({
256
sortBy: ['department', 'name'],
257
sortDirection: { department: 'ASC', name: 'ASC' }
258
});
259
const [sortedData, setSortedData] = useState(employees);
260
261
const performMultiSort = (data, sortBy, sortDirection) => {
262
return [...data].sort((a, b) => {
263
for (const column of sortBy) {
264
const aVal = a[column];
265
const bVal = b[column];
266
const direction = sortDirection[column];
267
268
let result = 0;
269
if (aVal < bVal) result = -1;
270
else if (aVal > bVal) result = 1;
271
272
if (result !== 0) {
273
return direction === SortDirection.DESC ? -result : result;
274
}
275
}
276
return 0;
277
});
278
};
279
280
const multiSortHandler = createTableMultiSort(
281
({ sortBy, sortDirection }) => {
282
// Handle multi-column sort
283
const newSortBy = [sortBy];
284
const newSortDirection = { [sortBy]: sortDirection };
285
286
// Add existing sorts for multi-column sorting
287
sortState.sortBy.forEach(existingSort => {
288
if (existingSort !== sortBy) {
289
newSortBy.push(existingSort);
290
newSortDirection[existingSort] = sortState.sortDirection[existingSort];
291
}
292
});
293
294
const newSortState = {
295
sortBy: newSortBy.slice(0, 3), // Limit to 3 columns
296
sortDirection: newSortDirection
297
};
298
299
setSortState(newSortState);
300
setSortedData(performMultiSort(employees, newSortState.sortBy, newSortState.sortDirection));
301
},
302
{
303
defaultSortBy: ['department', 'name'],
304
defaultSortDirection: { department: 'ASC', name: 'ASC' }
305
}
306
);
307
308
const rowGetter = ({ index }) => sortedData[index];
309
310
// Custom header renderer showing sort priority
311
const multiSortHeaderRenderer = ({ label, dataKey, sortBy, sortDirection }) => {
312
const sortIndex = sortState.sortBy.indexOf(dataKey);
313
const isSorted = sortIndex !== -1;
314
315
return (
316
<div className="multi-sort-header">
317
<span>{label}</span>
318
{isSorted && (
319
<div className="sort-info">
320
<span className="sort-priority">{sortIndex + 1}</span>
321
<span className="sort-direction">
322
{sortState.sortDirection[dataKey] === 'ASC' ? '▲' : '▼'}
323
</span>
324
</div>
325
)}
326
</div>
327
);
328
};
329
330
return (
331
<div>
332
<div className="sort-info-panel">
333
<h4>Current Sort:</h4>
334
<ul>
335
{sortState.sortBy.map((column, index) => (
336
<li key={column}>
337
{index + 1}. {column} ({sortState.sortDirection[column]})
338
</li>
339
))}
340
</ul>
341
</div>
342
343
<Table
344
width={800}
345
height={400}
346
headerHeight={60}
347
rowHeight={40}
348
rowCount={sortedData.length}
349
rowGetter={rowGetter}
350
onHeaderClick={multiSortHandler}
351
>
352
<Column
353
label="Department"
354
dataKey="department"
355
width={150}
356
headerRenderer={multiSortHeaderRenderer}
357
/>
358
<Column
359
label="Name"
360
dataKey="name"
361
width={200}
362
headerRenderer={multiSortHeaderRenderer}
363
/>
364
<Column
365
label="Position"
366
dataKey="position"
367
width={180}
368
headerRenderer={multiSortHeaderRenderer}
369
/>
370
<Column
371
label="Salary"
372
dataKey="salary"
373
width={120}
374
headerRenderer={multiSortHeaderRenderer}
375
cellRenderer={({ cellData }) => `$${cellData.toLocaleString()}`}
376
/>
377
<Column
378
label="Start Date"
379
dataKey="startDate"
380
width={120}
381
headerRenderer={multiSortHeaderRenderer}
382
cellRenderer={({ cellData }) => new Date(cellData).toLocaleDateString()}
383
/>
384
</Table>
385
</div>
386
);
387
}
388
```
389
390
### Masonry Cell Positioner
391
392
Utility for creating position managers for masonry layouts.
393
394
```javascript { .api }
395
/**
396
* Creates a cell positioner for masonry layouts
397
* @param params - Positioner configuration
398
*/
399
function createMasonryCellPositioner(params: {
400
/** Cache for cell measurements */
401
cellMeasurerCache: CellMeasurerCache,
402
/** Number of columns in the masonry */
403
columnCount: number,
404
/** Width of each column */
405
columnWidth: number,
406
/** Space between items */
407
spacer?: number
408
}): {
409
/** Reset the positioner state */
410
reset: (params: {columnCount: number, columnWidth: number, spacer?: number}) => void;
411
};
412
```
413
414
**Usage Examples:**
415
416
```javascript
417
import React, { useMemo, useCallback } from 'react';
418
import {
419
Masonry,
420
CellMeasurerCache,
421
createMasonryCellPositioner,
422
AutoSizer
423
} from 'react-virtualized';
424
425
// Responsive masonry with dynamic positioner
426
function ResponsiveMasonryGrid({ items, containerWidth }) {
427
const cache = useMemo(() => new CellMeasurerCache({
428
defaultHeight: 200,
429
fixedWidth: true
430
}), []);
431
432
// Calculate columns based on container width
433
const columnCount = Math.max(1, Math.floor(containerWidth / 250));
434
const columnWidth = Math.floor((containerWidth - (columnCount + 1) * 10) / columnCount);
435
436
const cellPositioner = useMemo(() => {
437
return createMasonryCellPositioner({
438
cellMeasurerCache: cache,
439
columnCount,
440
columnWidth,
441
spacer: 10
442
});
443
}, [cache, columnCount, columnWidth]);
444
445
// Reset positioner when layout changes
446
const resetPositioner = useCallback(() => {
447
cellPositioner.reset({
448
columnCount,
449
columnWidth,
450
spacer: 10
451
});
452
}, [cellPositioner, columnCount, columnWidth]);
453
454
// Reset when dimensions change
455
React.useEffect(() => {
456
resetPositioner();
457
}, [resetPositioner]);
458
459
const cellRenderer = ({ index, key, parent, style }) => (
460
<CellMeasurer
461
cache={cache}
462
index={index}
463
key={key}
464
parent={parent}
465
>
466
<div style={style} className="masonry-item">
467
<img
468
src={items[index].imageUrl}
469
alt={items[index].title}
470
style={{ width: '100%', height: 'auto' }}
471
onLoad={() => cache.clear(index, 0)}
472
/>
473
<div className="item-content">
474
<h3>{items[index].title}</h3>
475
<p>{items[index].description}</p>
476
</div>
477
</div>
478
</CellMeasurer>
479
);
480
481
return (
482
<div style={{ height: 600, width: '100%' }}>
483
<AutoSizer>
484
{({ height, width }) => (
485
<Masonry
486
cellCount={items.length}
487
cellMeasurerCache={cache}
488
cellPositioner={cellPositioner}
489
cellRenderer={cellRenderer}
490
height={height}
491
width={width}
492
/>
493
)}
494
</AutoSizer>
495
</div>
496
);
497
}
498
499
// Masonry with custom spacing and layout
500
function CustomMasonry({ photos, spacing = 15 }) {
501
const cache = useMemo(() => new CellMeasurerCache({
502
defaultHeight: 300,
503
fixedWidth: true
504
}), []);
505
506
const cellPositioner = useMemo(() => {
507
return createMasonryCellPositioner({
508
cellMeasurerCache: cache,
509
columnCount: 4,
510
columnWidth: 200,
511
spacer: spacing
512
});
513
}, [cache, spacing]);
514
515
const cellRenderer = ({ index, key, parent, style }) => {
516
const photo = photos[index];
517
518
return (
519
<CellMeasurer
520
cache={cache}
521
index={index}
522
key={key}
523
parent={parent}
524
>
525
{({ measure, registerChild }) => (
526
<div
527
ref={registerChild}
528
style={{
529
...style,
530
borderRadius: '8px',
531
overflow: 'hidden',
532
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
533
}}
534
className="photo-item"
535
>
536
<img
537
src={photo.url}
538
alt={photo.title}
539
onLoad={measure}
540
style={{ width: '100%', height: 'auto', display: 'block' }}
541
/>
542
<div style={{ padding: 12, backgroundColor: 'white' }}>
543
<h4 style={{ margin: '0 0 8px 0' }}>{photo.title}</h4>
544
<p style={{ margin: 0, color: '#666', fontSize: '14px' }}>
545
{photo.description}
546
</p>
547
</div>
548
</div>
549
)}
550
</CellMeasurer>
551
);
552
};
553
554
return (
555
<div style={{ height: 600, width: 850 }}>
556
<Masonry
557
cellCount={photos.length}
558
cellMeasurerCache={cache}
559
cellPositioner={cellPositioner}
560
cellRenderer={cellRenderer}
561
height={600}
562
width={850}
563
/>
564
</div>
565
);
566
}
567
```
568
569
### Constants and Configuration
570
571
```javascript { .api }
572
/** Default timeout for scrolling reset (in milliseconds) */
573
const DEFAULT_SCROLLING_RESET_TIME_INTERVAL: 150;
574
575
/** Timeout for window scroll detection */
576
const IS_SCROLLING_TIMEOUT: 150;
577
578
/** Scroll direction constants for internal use */
579
const SCROLL_DIRECTION_BACKWARD: 'backward';
580
const SCROLL_DIRECTION_FORWARD: 'forward';
581
```