React components for efficiently rendering large lists and tabular data
—
Components for handling dynamic content sizing, measurement, and infinite loading scenarios.
Measures dynamic cell content to provide accurate dimensions for virtualization, essential for variable-height content.
/**
* Measures dynamic cell content for accurate virtualization
* @param props - CellMeasurer configuration
*/
function CellMeasurer(props: {
/** Cache instance for storing measurements */
cache: CellMeasurerCache;
/** Function that renders the measurable content */
children: (params: {measure: () => void, registerChild: (element: HTMLElement) => void}) => React.Node;
/** Column index (for Grid components) */
columnIndex?: number;
/** Parent component reference */
parent: React.Component;
/** Row index */
rowIndex: number;
}): React.Component;Cache for storing cell measurements to optimize performance and avoid re-measuring cells.
/**
* Cache for CellMeasurer measurements
*/
class CellMeasurerCache {
/**
* Creates a new measurement cache
* @param params - Cache configuration
*/
constructor(params: {
/** Default height for unmeasured cells */
defaultHeight?: number;
/** Default width for unmeasured cells */
defaultWidth?: number;
/** Whether all cells have fixed height */
fixedHeight?: boolean;
/** Whether all cells have fixed width */
fixedWidth?: boolean;
/** Minimum height for any cell */
minHeight?: number;
/** Minimum width for any cell */
minWidth?: number;
/** Function to generate cache keys */
keyMapper?: (rowIndex: number, columnIndex: number) => string;
});
/** Clear cached measurements for a specific cell */
clear(rowIndex: number, columnIndex?: number): void;
/** Clear all cached measurements */
clearAll(): void;
/** Get cached height for a cell */
getHeight(rowIndex: number, columnIndex?: number): number;
/** Get cached width for a cell */
getWidth(rowIndex: number, columnIndex?: number): number;
/** Check if a cell has been measured */
has(rowIndex: number, columnIndex?: number): boolean;
/** Check if cache uses fixed height */
hasFixedHeight(): boolean;
/** Check if cache uses fixed width */
hasFixedWidth(): boolean;
/** Row height function for List/Grid components */
rowHeight(params: {index: number}): number;
/** Column width function for Grid components */
columnWidth(params: {index: number}): number;
/** Set cached dimensions for a cell */
set(rowIndex: number, columnIndex: number, width: number, height: number): void;
/** Default height value */
get defaultHeight(): number;
/** Default width value */
get defaultWidth(): number;
}Usage Examples:
import React from 'react';
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
// Dynamic height list with CellMeasurer
function DynamicHeightList({ items }) {
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 100
});
const rowRenderer = ({ index, key, parent, style }) => (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<div style={style} className="dynamic-row">
<h3>{items[index].title}</h3>
<p>{items[index].description}</p>
<div className="tags">
{items[index].tags.map(tag => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
</div>
</CellMeasurer>
);
return (
<List
deferredMeasurementCache={cache}
height={600}
rowCount={items.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
width={400}
/>
);
}
// Dynamic grid with variable cell sizes
function DynamicGrid({ data }) {
const cache = new CellMeasurerCache({
defaultHeight: 80,
defaultWidth: 120,
fixedHeight: false,
fixedWidth: false
});
const cellRenderer = ({ columnIndex, key, parent, rowIndex, style }) => (
<CellMeasurer
cache={cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}
>
<div style={style} className="dynamic-cell">
<div className="cell-content">
{data[rowIndex][columnIndex]}
</div>
</div>
</CellMeasurer>
);
return (
<Grid
cellRenderer={cellRenderer}
columnCount={data[0].length}
columnWidth={cache.columnWidth}
deferredMeasurementCache={cache}
height={500}
rowCount={data.length}
rowHeight={cache.rowHeight}
width={800}
/>
);
}
// List with image content requiring measurement
function ImageList({ posts }) {
const cache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 200
});
const rowRenderer = ({ index, key, parent, style }) => {
const post = posts[index];
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure, registerChild }) => (
<div ref={registerChild} style={style} className="post-item">
<img
src={post.imageUrl}
alt={post.title}
onLoad={measure}
style={{ width: '100%', height: 'auto' }}
/>
<div className="post-content">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
</div>
</div>
)}
</CellMeasurer>
);
};
return (
<List
deferredMeasurementCache={cache}
height={600}
rowCount={posts.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
width={400}
/>
);
}Manages loading additional data as the user scrolls, perfect for implementing infinite scroll functionality.
/**
* Manages loading additional data as user scrolls
* @param props - InfiniteLoader configuration
*/
function InfiniteLoader(props: {
/** Function that renders the scrollable component */
children: (params: {
onRowsRendered: (params: {overscanStartIndex: number, overscanStopIndex: number, startIndex: number, stopIndex: number}) => void,
registerChild: (element: React.Component) => void
}) => React.Node;
/** Function to check if a row is loaded */
isRowLoaded: (params: {index: number}) => boolean;
/** Function to load more rows */
loadMoreRows: (params: {startIndex: number, stopIndex: number}) => Promise<any>;
/** Total number of rows (including unloaded) */
rowCount: number;
/** Minimum number of rows to batch load (default: 10) */
minimumBatchSize?: number;
/** Number of rows to look ahead for loading (default: 15) */
threshold?: number;
}): React.Component;Usage Examples:
import React, { useState, useCallback } from 'react';
import { InfiniteLoader, List, AutoSizer } from 'react-virtualized';
// Basic infinite loading list
function InfiniteList() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const isRowLoaded = ({ index }) => {
return !!items[index];
};
const loadMoreRows = useCallback(async ({ startIndex, stopIndex }) => {
if (loading) return;
setLoading(true);
try {
// Simulate API call
const newItems = await fetchItems(startIndex, stopIndex);
setItems(prevItems => {
const updatedItems = [...prevItems];
newItems.forEach((item, index) => {
updatedItems[startIndex + index] = item;
});
return updatedItems;
});
} finally {
setLoading(false);
}
}, [loading]);
const rowRenderer = ({ index, key, style }) => {
const item = items[index];
if (!item) {
return (
<div key={key} style={style} className="loading-row">
Loading...
</div>
);
}
return (
<div key={key} style={style} className="item-row">
<h4>{item.title}</h4>
<p>{item.description}</p>
</div>
);
};
return (
<div style={{ height: 400, width: '100%' }}>
<InfiniteLoader
isRowLoaded={isRowLoaded}
loadMoreRows={loadMoreRows}
rowCount={10000} // Total possible rows
threshold={15}
>
{({ onRowsRendered, registerChild }) => (
<AutoSizer>
{({ height, width }) => (
<List
ref={registerChild}
height={height}
width={width}
rowCount={10000}
rowHeight={80}
rowRenderer={rowRenderer}
onRowsRendered={onRowsRendered}
/>
)}
</AutoSizer>
)}
</InfiniteLoader>
</div>
);
}
// Advanced infinite loader with error handling
function AdvancedInfiniteList({ apiEndpoint }) {
const [items, setItems] = useState([]);
const [errors, setErrors] = useState({});
const [hasNextPage, setHasNextPage] = useState(true);
const isRowLoaded = ({ index }) => {
return !!items[index] || !!errors[index];
};
const loadMoreRows = async ({ startIndex, stopIndex }) => {
// Don't load if we've reached the end
if (!hasNextPage && startIndex >= items.length) {
return;
}
try {
const response = await fetch(
`${apiEndpoint}?start=${startIndex}&count=${stopIndex - startIndex + 1}`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setItems(prevItems => {
const updatedItems = [...prevItems];
data.items.forEach((item, index) => {
updatedItems[startIndex + index] = item;
});
return updatedItems;
});
setHasNextPage(data.hasMore);
// Clear any previous errors for this range
setErrors(prevErrors => {
const updatedErrors = { ...prevErrors };
for (let i = startIndex; i <= stopIndex; i++) {
delete updatedErrors[i];
}
return updatedErrors;
});
} catch (error) {
// Mark these indices as having errors
setErrors(prevErrors => {
const updatedErrors = { ...prevErrors };
for (let i = startIndex; i <= stopIndex; i++) {
updatedErrors[i] = error.message;
}
return updatedErrors;
});
}
};
const rowRenderer = ({ index, key, style }) => {
const item = items[index];
const error = errors[index];
if (error) {
return (
<div key={key} style={style} className="error-row">
Error loading item: {error}
<button onClick={() => loadMoreRows({ startIndex: index, stopIndex: index })}>
Retry
</button>
</div>
);
}
if (!item) {
return (
<div key={key} style={style} className="loading-row">
<div className="spinner" />
Loading...
</div>
);
}
return (
<div key={key} style={style} className="item-row">
<img src={item.thumbnail} alt="" />
<div className="item-content">
<h4>{item.title}</h4>
<p>{item.description}</p>
<small>ID: {item.id}</small>
</div>
</div>
);
};
const estimatedRowCount = hasNextPage ? items.length + 100 : items.length;
return (
<div style={{ height: 500, width: '100%' }}>
<InfiniteLoader
isRowLoaded={isRowLoaded}
loadMoreRows={loadMoreRows}
rowCount={estimatedRowCount}
minimumBatchSize={20}
threshold={10}
>
{({ onRowsRendered, registerChild }) => (
<AutoSizer>
{({ height, width }) => (
<List
ref={registerChild}
height={height}
width={width}
rowCount={estimatedRowCount}
rowHeight={100}
rowRenderer={rowRenderer}
onRowsRendered={onRowsRendered}
/>
)}
</AutoSizer>
)}
</InfiniteLoader>
</div>
);
}
// Helper function to simulate API calls
async function fetchItems(startIndex, stopIndex) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 500));
const count = stopIndex - startIndex + 1;
return Array.from({ length: count }, (_, i) => ({
id: startIndex + i,
title: `Item ${startIndex + i}`,
description: `Description for item ${startIndex + i}`,
thumbnail: `https://picsum.photos/60/60?random=${startIndex + i}`
}));
}Install with Tessl CLI
npx tessl i tessl/npm-react-virtualized