or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-components.mddynamic-content.mdindex.mdlayout-components.mdnavigation-components.mdspecialized-layouts.mdtable-components.mdutilities.md

dynamic-content.mddocs/

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

```