0
# Dynamic Content and Measurement
1
2
Components for handling dynamic content sizing, measurement, and infinite loading scenarios.
3
4
## Capabilities
5
6
### CellMeasurer Component
7
8
Measures dynamic cell content to provide accurate dimensions for virtualization, essential for variable-height content.
9
10
```javascript { .api }
11
/**
12
* Measures dynamic cell content for accurate virtualization
13
* @param props - CellMeasurer configuration
14
*/
15
function CellMeasurer(props: {
16
/** Cache instance for storing measurements */
17
cache: CellMeasurerCache;
18
/** Function that renders the measurable content */
19
children: (params: {measure: () => void, registerChild: (element: HTMLElement) => void}) => React.Node;
20
/** Column index (for Grid components) */
21
columnIndex?: number;
22
/** Parent component reference */
23
parent: React.Component;
24
/** Row index */
25
rowIndex: number;
26
}): React.Component;
27
```
28
29
### CellMeasurerCache Class
30
31
Cache for storing cell measurements to optimize performance and avoid re-measuring cells.
32
33
```javascript { .api }
34
/**
35
* Cache for CellMeasurer measurements
36
*/
37
class CellMeasurerCache {
38
/**
39
* Creates a new measurement cache
40
* @param params - Cache configuration
41
*/
42
constructor(params: {
43
/** Default height for unmeasured cells */
44
defaultHeight?: number;
45
/** Default width for unmeasured cells */
46
defaultWidth?: number;
47
/** Whether all cells have fixed height */
48
fixedHeight?: boolean;
49
/** Whether all cells have fixed width */
50
fixedWidth?: boolean;
51
/** Minimum height for any cell */
52
minHeight?: number;
53
/** Minimum width for any cell */
54
minWidth?: number;
55
/** Function to generate cache keys */
56
keyMapper?: (rowIndex: number, columnIndex: number) => string;
57
});
58
59
/** Clear cached measurements for a specific cell */
60
clear(rowIndex: number, columnIndex?: number): void;
61
62
/** Clear all cached measurements */
63
clearAll(): void;
64
65
/** Get cached height for a cell */
66
getHeight(rowIndex: number, columnIndex?: number): number;
67
68
/** Get cached width for a cell */
69
getWidth(rowIndex: number, columnIndex?: number): number;
70
71
/** Check if a cell has been measured */
72
has(rowIndex: number, columnIndex?: number): boolean;
73
74
/** Check if cache uses fixed height */
75
hasFixedHeight(): boolean;
76
77
/** Check if cache uses fixed width */
78
hasFixedWidth(): boolean;
79
80
/** Row height function for List/Grid components */
81
rowHeight(params: {index: number}): number;
82
83
/** Column width function for Grid components */
84
columnWidth(params: {index: number}): number;
85
86
/** Set cached dimensions for a cell */
87
set(rowIndex: number, columnIndex: number, width: number, height: number): void;
88
89
/** Default height value */
90
get defaultHeight(): number;
91
92
/** Default width value */
93
get defaultWidth(): number;
94
}
95
```
96
97
**Usage Examples:**
98
99
```javascript
100
import React from 'react';
101
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
102
103
// Dynamic height list with CellMeasurer
104
function DynamicHeightList({ items }) {
105
const cache = new CellMeasurerCache({
106
fixedWidth: true,
107
defaultHeight: 100
108
});
109
110
const rowRenderer = ({ index, key, parent, style }) => (
111
<CellMeasurer
112
cache={cache}
113
columnIndex={0}
114
key={key}
115
parent={parent}
116
rowIndex={index}
117
>
118
<div style={style} className="dynamic-row">
119
<h3>{items[index].title}</h3>
120
<p>{items[index].description}</p>
121
<div className="tags">
122
{items[index].tags.map(tag => (
123
<span key={tag} className="tag">{tag}</span>
124
))}
125
</div>
126
</div>
127
</CellMeasurer>
128
);
129
130
return (
131
<List
132
deferredMeasurementCache={cache}
133
height={600}
134
rowCount={items.length}
135
rowHeight={cache.rowHeight}
136
rowRenderer={rowRenderer}
137
width={400}
138
/>
139
);
140
}
141
142
// Dynamic grid with variable cell sizes
143
function DynamicGrid({ data }) {
144
const cache = new CellMeasurerCache({
145
defaultHeight: 80,
146
defaultWidth: 120,
147
fixedHeight: false,
148
fixedWidth: false
149
});
150
151
const cellRenderer = ({ columnIndex, key, parent, rowIndex, style }) => (
152
<CellMeasurer
153
cache={cache}
154
columnIndex={columnIndex}
155
key={key}
156
parent={parent}
157
rowIndex={rowIndex}
158
>
159
<div style={style} className="dynamic-cell">
160
<div className="cell-content">
161
{data[rowIndex][columnIndex]}
162
</div>
163
</div>
164
</CellMeasurer>
165
);
166
167
return (
168
<Grid
169
cellRenderer={cellRenderer}
170
columnCount={data[0].length}
171
columnWidth={cache.columnWidth}
172
deferredMeasurementCache={cache}
173
height={500}
174
rowCount={data.length}
175
rowHeight={cache.rowHeight}
176
width={800}
177
/>
178
);
179
}
180
181
// List with image content requiring measurement
182
function ImageList({ posts }) {
183
const cache = new CellMeasurerCache({
184
fixedWidth: true,
185
minHeight: 200
186
});
187
188
const rowRenderer = ({ index, key, parent, style }) => {
189
const post = posts[index];
190
191
return (
192
<CellMeasurer
193
cache={cache}
194
columnIndex={0}
195
key={key}
196
parent={parent}
197
rowIndex={index}
198
>
199
{({ measure, registerChild }) => (
200
<div ref={registerChild} style={style} className="post-item">
201
<img
202
src={post.imageUrl}
203
alt={post.title}
204
onLoad={measure}
205
style={{ width: '100%', height: 'auto' }}
206
/>
207
<div className="post-content">
208
<h3>{post.title}</h3>
209
<p>{post.excerpt}</p>
210
</div>
211
</div>
212
)}
213
</CellMeasurer>
214
);
215
};
216
217
return (
218
<List
219
deferredMeasurementCache={cache}
220
height={600}
221
rowCount={posts.length}
222
rowHeight={cache.rowHeight}
223
rowRenderer={rowRenderer}
224
width={400}
225
/>
226
);
227
}
228
```
229
230
### InfiniteLoader Component
231
232
Manages loading additional data as the user scrolls, perfect for implementing infinite scroll functionality.
233
234
```javascript { .api }
235
/**
236
* Manages loading additional data as user scrolls
237
* @param props - InfiniteLoader configuration
238
*/
239
function InfiniteLoader(props: {
240
/** Function that renders the scrollable component */
241
children: (params: {
242
onRowsRendered: (params: {overscanStartIndex: number, overscanStopIndex: number, startIndex: number, stopIndex: number}) => void,
243
registerChild: (element: React.Component) => void
244
}) => React.Node;
245
/** Function to check if a row is loaded */
246
isRowLoaded: (params: {index: number}) => boolean;
247
/** Function to load more rows */
248
loadMoreRows: (params: {startIndex: number, stopIndex: number}) => Promise<any>;
249
/** Total number of rows (including unloaded) */
250
rowCount: number;
251
/** Minimum number of rows to batch load (default: 10) */
252
minimumBatchSize?: number;
253
/** Number of rows to look ahead for loading (default: 15) */
254
threshold?: number;
255
}): React.Component;
256
```
257
258
**Usage Examples:**
259
260
```javascript
261
import React, { useState, useCallback } from 'react';
262
import { InfiniteLoader, List, AutoSizer } from 'react-virtualized';
263
264
// Basic infinite loading list
265
function InfiniteList() {
266
const [items, setItems] = useState([]);
267
const [loading, setLoading] = useState(false);
268
269
const isRowLoaded = ({ index }) => {
270
return !!items[index];
271
};
272
273
const loadMoreRows = useCallback(async ({ startIndex, stopIndex }) => {
274
if (loading) return;
275
276
setLoading(true);
277
try {
278
// Simulate API call
279
const newItems = await fetchItems(startIndex, stopIndex);
280
setItems(prevItems => {
281
const updatedItems = [...prevItems];
282
newItems.forEach((item, index) => {
283
updatedItems[startIndex + index] = item;
284
});
285
return updatedItems;
286
});
287
} finally {
288
setLoading(false);
289
}
290
}, [loading]);
291
292
const rowRenderer = ({ index, key, style }) => {
293
const item = items[index];
294
295
if (!item) {
296
return (
297
<div key={key} style={style} className="loading-row">
298
Loading...
299
</div>
300
);
301
}
302
303
return (
304
<div key={key} style={style} className="item-row">
305
<h4>{item.title}</h4>
306
<p>{item.description}</p>
307
</div>
308
);
309
};
310
311
return (
312
<div style={{ height: 400, width: '100%' }}>
313
<InfiniteLoader
314
isRowLoaded={isRowLoaded}
315
loadMoreRows={loadMoreRows}
316
rowCount={10000} // Total possible rows
317
threshold={15}
318
>
319
{({ onRowsRendered, registerChild }) => (
320
<AutoSizer>
321
{({ height, width }) => (
322
<List
323
ref={registerChild}
324
height={height}
325
width={width}
326
rowCount={10000}
327
rowHeight={80}
328
rowRenderer={rowRenderer}
329
onRowsRendered={onRowsRendered}
330
/>
331
)}
332
</AutoSizer>
333
)}
334
</InfiniteLoader>
335
</div>
336
);
337
}
338
339
// Advanced infinite loader with error handling
340
function AdvancedInfiniteList({ apiEndpoint }) {
341
const [items, setItems] = useState([]);
342
const [errors, setErrors] = useState({});
343
const [hasNextPage, setHasNextPage] = useState(true);
344
345
const isRowLoaded = ({ index }) => {
346
return !!items[index] || !!errors[index];
347
};
348
349
const loadMoreRows = async ({ startIndex, stopIndex }) => {
350
// Don't load if we've reached the end
351
if (!hasNextPage && startIndex >= items.length) {
352
return;
353
}
354
355
try {
356
const response = await fetch(
357
`${apiEndpoint}?start=${startIndex}&count=${stopIndex - startIndex + 1}`
358
);
359
360
if (!response.ok) {
361
throw new Error(`HTTP ${response.status}`);
362
}
363
364
const data = await response.json();
365
366
setItems(prevItems => {
367
const updatedItems = [...prevItems];
368
data.items.forEach((item, index) => {
369
updatedItems[startIndex + index] = item;
370
});
371
return updatedItems;
372
});
373
374
setHasNextPage(data.hasMore);
375
376
// Clear any previous errors for this range
377
setErrors(prevErrors => {
378
const updatedErrors = { ...prevErrors };
379
for (let i = startIndex; i <= stopIndex; i++) {
380
delete updatedErrors[i];
381
}
382
return updatedErrors;
383
});
384
385
} catch (error) {
386
// Mark these indices as having errors
387
setErrors(prevErrors => {
388
const updatedErrors = { ...prevErrors };
389
for (let i = startIndex; i <= stopIndex; i++) {
390
updatedErrors[i] = error.message;
391
}
392
return updatedErrors;
393
});
394
}
395
};
396
397
const rowRenderer = ({ index, key, style }) => {
398
const item = items[index];
399
const error = errors[index];
400
401
if (error) {
402
return (
403
<div key={key} style={style} className="error-row">
404
Error loading item: {error}
405
<button onClick={() => loadMoreRows({ startIndex: index, stopIndex: index })}>
406
Retry
407
</button>
408
</div>
409
);
410
}
411
412
if (!item) {
413
return (
414
<div key={key} style={style} className="loading-row">
415
<div className="spinner" />
416
Loading...
417
</div>
418
);
419
}
420
421
return (
422
<div key={key} style={style} className="item-row">
423
<img src={item.thumbnail} alt="" />
424
<div className="item-content">
425
<h4>{item.title}</h4>
426
<p>{item.description}</p>
427
<small>ID: {item.id}</small>
428
</div>
429
</div>
430
);
431
};
432
433
const estimatedRowCount = hasNextPage ? items.length + 100 : items.length;
434
435
return (
436
<div style={{ height: 500, width: '100%' }}>
437
<InfiniteLoader
438
isRowLoaded={isRowLoaded}
439
loadMoreRows={loadMoreRows}
440
rowCount={estimatedRowCount}
441
minimumBatchSize={20}
442
threshold={10}
443
>
444
{({ onRowsRendered, registerChild }) => (
445
<AutoSizer>
446
{({ height, width }) => (
447
<List
448
ref={registerChild}
449
height={height}
450
width={width}
451
rowCount={estimatedRowCount}
452
rowHeight={100}
453
rowRenderer={rowRenderer}
454
onRowsRendered={onRowsRendered}
455
/>
456
)}
457
</AutoSizer>
458
)}
459
</InfiniteLoader>
460
</div>
461
);
462
}
463
464
// Helper function to simulate API calls
465
async function fetchItems(startIndex, stopIndex) {
466
// Simulate network delay
467
await new Promise(resolve => setTimeout(resolve, 500));
468
469
const count = stopIndex - startIndex + 1;
470
return Array.from({ length: count }, (_, i) => ({
471
id: startIndex + i,
472
title: `Item ${startIndex + i}`,
473
description: `Description for item ${startIndex + i}`,
474
thumbnail: `https://picsum.photos/60/60?random=${startIndex + i}`
475
}));
476
}
477
```