React components for efficiently rendering large lists and tabular data
—
Helper functions and utilities for customizing virtualization behavior and enhancing performance.
Functions that calculate which additional (non-visible) items to render for smooth scrolling.
/**
* Default overscan calculation for standard usage
* @param params - Overscan calculation parameters
*/
function defaultOverscanIndicesGetter(params: {
/** Total number of cells */
cellCount: number,
/** Number of extra cells to render */
overscanCellsCount: number,
/** Scroll direction (-1 for backward, 1 for forward) */
scrollDirection: number,
/** Index of first visible cell */
startIndex: number,
/** Index of last visible cell */
stopIndex: number
}): {
/** Index of first cell to render (including overscan) */
overscanStartIndex: number,
/** Index of last cell to render (including overscan) */
overscanStopIndex: number
};
/**
* Enhanced overscan calculation for accessibility
* Renders more cells to improve screen reader performance
* @param params - Overscan calculation parameters
*/
function accessibilityOverscanIndicesGetter(params: {
cellCount: number,
overscanCellsCount: number,
/** Scroll direction (-1 for backward, 1 for forward) */
scrollDirection: number,
startIndex: number,
stopIndex: number
}): {
overscanStartIndex: number,
overscanStopIndex: number
};Usage Examples:
import React from 'react';
import {
List,
Grid,
defaultOverscanIndicesGetter,
accessibilityOverscanIndicesGetter
} from 'react-virtualized';
// List with custom overscan calculation
function CustomOverscanList({ items, highPerformanceMode }) {
// Custom overscan getter that adjusts based on performance mode
const customOverscanGetter = (params) => {
if (highPerformanceMode) {
// Render fewer extra items for better performance
return defaultOverscanIndicesGetter({
...params,
overscanCellsCount: Math.min(params.overscanCellsCount, 2)
});
} else {
// Use accessibility mode for better UX
return accessibilityOverscanIndicesGetter(params);
}
};
const rowRenderer = ({ index, key, style }) => (
<div key={key} style={style} className="list-item">
{items[index]}
</div>
);
return (
<div>
<div className="performance-indicator">
Mode: {highPerformanceMode ? 'High Performance' : 'Accessibility'}
</div>
<List
height={400}
width={300}
rowCount={items.length}
rowHeight={50}
rowRenderer={rowRenderer}
overscanIndicesGetter={customOverscanGetter}
overscanRowCount={5}
/>
</div>
);
}
// Grid with accessibility-focused overscan
function AccessibleGrid({ data }) {
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => (
<div key={key} style={style} className="grid-cell">
{data[rowIndex][columnIndex]}
</div>
);
return (
<Grid
cellRenderer={cellRenderer}
columnCount={data[0].length}
columnWidth={100}
height={400}
rowCount={data.length}
rowHeight={50}
width={500}
overscanIndicesGetter={accessibilityOverscanIndicesGetter}
overscanColumnCount={3}
overscanRowCount={3}
/>
);
}Controls how cells are rendered within the visible and overscan ranges.
/**
* Default cell range renderer
* @param params - Cell range rendering parameters
*/
function defaultCellRangeRenderer(params: {
/** Cache of rendered cells */
cellCache: object,
/** Cell rendering function */
cellRenderer: (params: {columnIndex: number, key: string, rowIndex: number, style: object}) => React.Node,
/** First visible column index */
columnStartIndex: number,
/** Last visible column index */
columnStopIndex: number,
/** Deferred measurement cache */
deferredMeasurementCache?: object,
/** Horizontal scroll adjustment */
horizontalOffsetAdjustment: number,
/** Whether scrolling is in progress */
isScrolling: boolean,
/** Parent component reference */
parent: object,
/** First visible row index */
rowStartIndex: number,
/** Last visible row index */
rowStopIndex: number,
/** Cache of computed styles */
styleCache: object,
/** Vertical scroll adjustment */
verticalOffsetAdjustment: number,
/** Map of visible column indices */
visibleColumnIndices: object,
/** Map of visible row indices */
visibleRowIndices: object
}): React.Node[];Usage Examples:
import React from 'react';
import { Grid, defaultCellRangeRenderer } from 'react-virtualized';
// Grid with custom cell range renderer for performance optimization
function OptimizedGrid({ data, enableDebugMode }) {
const customCellRangeRenderer = (params) => {
if (enableDebugMode) {
// Add debug information to rendered cells
console.log('Rendering cells:', {
columns: `${params.columnStartIndex}-${params.columnStopIndex}`,
rows: `${params.rowStartIndex}-${params.rowStopIndex}`,
isScrolling: params.isScrolling
});
}
// Use default renderer but with custom caching strategy
const cells = defaultCellRangeRenderer(params);
if (enableDebugMode) {
// Add debug borders to cells during scrolling
return cells.map(cell =>
React.cloneElement(cell, {
...cell.props,
style: {
...cell.props.style,
border: params.isScrolling ? '1px solid red' : '1px solid #ddd'
}
})
);
}
return cells;
};
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => (
<div key={key} style={style} className="grid-cell">
{data[rowIndex][columnIndex]}
</div>
);
return (
<Grid
cellRenderer={cellRenderer}
cellRangeRenderer={customCellRangeRenderer}
columnCount={data[0].length}
columnWidth={120}
height={400}
rowCount={data.length}
rowHeight={50}
width={600}
/>
);
}Advanced sorting utility for tables with multiple column sorting capabilities.
/**
* Multi-column sort utility for tables
* @param sortFunction - Function to handle sort events
* @param options - Configuration options
*/
function createTableMultiSort(
sortFunction: (params: {sortBy: string, sortDirection: string}) => void,
options?: {
/** Default columns to sort by */
defaultSortBy?: Array<string>,
/** Default sort directions for columns */
defaultSortDirection?: {[key: string]: string}
}
): (params: {event: Event, sortBy: string, sortDirection: string}) => void;Usage Examples:
import React, { useState } from 'react';
import { Table, Column, createTableMultiSort, SortDirection } from 'react-virtualized';
// Advanced table with multi-column sorting
function MultiSortTable({ employees }) {
const [sortState, setSortState] = useState({
sortBy: ['department', 'name'],
sortDirection: { department: 'ASC', name: 'ASC' }
});
const [sortedData, setSortedData] = useState(employees);
const performMultiSort = (data, sortBy, sortDirection) => {
return [...data].sort((a, b) => {
for (const column of sortBy) {
const aVal = a[column];
const bVal = b[column];
const direction = sortDirection[column];
let result = 0;
if (aVal < bVal) result = -1;
else if (aVal > bVal) result = 1;
if (result !== 0) {
return direction === SortDirection.DESC ? -result : result;
}
}
return 0;
});
};
const multiSortHandler = createTableMultiSort(
({ sortBy, sortDirection }) => {
// Handle multi-column sort
const newSortBy = [sortBy];
const newSortDirection = { [sortBy]: sortDirection };
// Add existing sorts for multi-column sorting
sortState.sortBy.forEach(existingSort => {
if (existingSort !== sortBy) {
newSortBy.push(existingSort);
newSortDirection[existingSort] = sortState.sortDirection[existingSort];
}
});
const newSortState = {
sortBy: newSortBy.slice(0, 3), // Limit to 3 columns
sortDirection: newSortDirection
};
setSortState(newSortState);
setSortedData(performMultiSort(employees, newSortState.sortBy, newSortState.sortDirection));
},
{
defaultSortBy: ['department', 'name'],
defaultSortDirection: { department: 'ASC', name: 'ASC' }
}
);
const rowGetter = ({ index }) => sortedData[index];
// Custom header renderer showing sort priority
const multiSortHeaderRenderer = ({ label, dataKey, sortBy, sortDirection }) => {
const sortIndex = sortState.sortBy.indexOf(dataKey);
const isSorted = sortIndex !== -1;
return (
<div className="multi-sort-header">
<span>{label}</span>
{isSorted && (
<div className="sort-info">
<span className="sort-priority">{sortIndex + 1}</span>
<span className="sort-direction">
{sortState.sortDirection[dataKey] === 'ASC' ? '▲' : '▼'}
</span>
</div>
)}
</div>
);
};
return (
<div>
<div className="sort-info-panel">
<h4>Current Sort:</h4>
<ul>
{sortState.sortBy.map((column, index) => (
<li key={column}>
{index + 1}. {column} ({sortState.sortDirection[column]})
</li>
))}
</ul>
</div>
<Table
width={800}
height={400}
headerHeight={60}
rowHeight={40}
rowCount={sortedData.length}
rowGetter={rowGetter}
onHeaderClick={multiSortHandler}
>
<Column
label="Department"
dataKey="department"
width={150}
headerRenderer={multiSortHeaderRenderer}
/>
<Column
label="Name"
dataKey="name"
width={200}
headerRenderer={multiSortHeaderRenderer}
/>
<Column
label="Position"
dataKey="position"
width={180}
headerRenderer={multiSortHeaderRenderer}
/>
<Column
label="Salary"
dataKey="salary"
width={120}
headerRenderer={multiSortHeaderRenderer}
cellRenderer={({ cellData }) => `$${cellData.toLocaleString()}`}
/>
<Column
label="Start Date"
dataKey="startDate"
width={120}
headerRenderer={multiSortHeaderRenderer}
cellRenderer={({ cellData }) => new Date(cellData).toLocaleDateString()}
/>
</Table>
</div>
);
}Utility for creating position managers for masonry layouts.
/**
* Creates a cell positioner for masonry layouts
* @param params - Positioner configuration
*/
function createMasonryCellPositioner(params: {
/** Cache for cell measurements */
cellMeasurerCache: CellMeasurerCache,
/** Number of columns in the masonry */
columnCount: number,
/** Width of each column */
columnWidth: number,
/** Space between items */
spacer?: number
}): {
/** Reset the positioner state */
reset: (params: {columnCount: number, columnWidth: number, spacer?: number}) => void;
};Usage Examples:
import React, { useMemo, useCallback } from 'react';
import {
Masonry,
CellMeasurerCache,
createMasonryCellPositioner,
AutoSizer
} from 'react-virtualized';
// Responsive masonry with dynamic positioner
function ResponsiveMasonryGrid({ items, containerWidth }) {
const cache = useMemo(() => new CellMeasurerCache({
defaultHeight: 200,
fixedWidth: true
}), []);
// Calculate columns based on container width
const columnCount = Math.max(1, Math.floor(containerWidth / 250));
const columnWidth = Math.floor((containerWidth - (columnCount + 1) * 10) / columnCount);
const cellPositioner = useMemo(() => {
return createMasonryCellPositioner({
cellMeasurerCache: cache,
columnCount,
columnWidth,
spacer: 10
});
}, [cache, columnCount, columnWidth]);
// Reset positioner when layout changes
const resetPositioner = useCallback(() => {
cellPositioner.reset({
columnCount,
columnWidth,
spacer: 10
});
}, [cellPositioner, columnCount, columnWidth]);
// Reset when dimensions change
React.useEffect(() => {
resetPositioner();
}, [resetPositioner]);
const cellRenderer = ({ index, key, parent, style }) => (
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}
>
<div style={style} className="masonry-item">
<img
src={items[index].imageUrl}
alt={items[index].title}
style={{ width: '100%', height: 'auto' }}
onLoad={() => cache.clear(index, 0)}
/>
<div className="item-content">
<h3>{items[index].title}</h3>
<p>{items[index].description}</p>
</div>
</div>
</CellMeasurer>
);
return (
<div style={{ height: 600, width: '100%' }}>
<AutoSizer>
{({ height, width }) => (
<Masonry
cellCount={items.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={cellRenderer}
height={height}
width={width}
/>
)}
</AutoSizer>
</div>
);
}
// Masonry with custom spacing and layout
function CustomMasonry({ photos, spacing = 15 }) {
const cache = useMemo(() => new CellMeasurerCache({
defaultHeight: 300,
fixedWidth: true
}), []);
const cellPositioner = useMemo(() => {
return createMasonryCellPositioner({
cellMeasurerCache: cache,
columnCount: 4,
columnWidth: 200,
spacer: spacing
});
}, [cache, spacing]);
const cellRenderer = ({ index, key, parent, style }) => {
const photo = photos[index];
return (
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}
>
{({ measure, registerChild }) => (
<div
ref={registerChild}
style={{
...style,
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}
className="photo-item"
>
<img
src={photo.url}
alt={photo.title}
onLoad={measure}
style={{ width: '100%', height: 'auto', display: 'block' }}
/>
<div style={{ padding: 12, backgroundColor: 'white' }}>
<h4 style={{ margin: '0 0 8px 0' }}>{photo.title}</h4>
<p style={{ margin: 0, color: '#666', fontSize: '14px' }}>
{photo.description}
</p>
</div>
</div>
)}
</CellMeasurer>
);
};
return (
<div style={{ height: 600, width: 850 }}>
<Masonry
cellCount={photos.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={cellRenderer}
height={600}
width={850}
/>
</div>
);
}/** Default timeout for scrolling reset (in milliseconds) */
const DEFAULT_SCROLLING_RESET_TIME_INTERVAL: 150;
/** Timeout for window scroll detection */
const IS_SCROLLING_TIMEOUT: 150;
/** Scroll direction constants for internal use */
const SCROLL_DIRECTION_BACKWARD: 'backward';
const SCROLL_DIRECTION_FORWARD: 'forward';Install with Tessl CLI
npx tessl i tessl/npm-react-virtualized