home / skills / dbobkov245-source / pwa-torserve / perf-virtual-lists
This skill optimizes performance for virtualized lists on Android TV by applying React Window techniques and overscan, ensuring smooth 60FPS.
npx playbooks add skill dbobkov245-source/pwa-torserve --skill perf-virtual-listsReview the files below or copy the command above to add this skill to your agents.
---
name: perf-virtual-lists
description: Specialist in high-performance list virtualization for Android TV (React).
---
# Performance: Virtualized Lists on TV
Rendering large lists (movies, episodes) on Android TV requires **virtualization** to maintain 60FPS.
Standard `map()` rendering will freeze the UI on low-end TV boxes (1GB RAM).
## π Core Technology
Use **`react-window`** + **`react-virtualized-auto-sizer`**.
It is lighter and faster than `react-virtualized`.
## πΊ TV-Specific Optimizations
### 1. Overscan is Critical
On TV, users scroll FAST (holding the Down button).
You MUST set `overscanCount` (or `overscanRowCount`) to at least **2-3 rows** to prevent "blank" areas during rapid scrolling.
```jsx
<FixedSizeGrid
overscanRowCount={3} // Prevents blank cells during fast scroll
...
/>
```
### 2. Focus Management Strategy
**Problem:** Virtualization unmounts off-screen items. If the focused item is scrolled out, focus is lost, and the browser resets it to `body`.
**Solution:** Do NOT rely on DOM focus (`document.activeElement`) for state.
1. Use `useTVNavigation` to track `focusedIndex` (data index).
2. Programmatically scroll to the focused item *before* it gets unmounted? No, just keep the index.
3. When the item re-mounts (scrolled back properly), the `ref` callback should re-apply focus if needed, or simply render it as `.focused`.
### 3. Fixed vs Variable Size
ALWAYS prefer **`FixedSizeGrid`** or **`FixedSizeList`**.
Variable sizing triggers expensive layout recalculations on every scroll frame, killing performance on TV hardware.
### 4. Image Handling
- Use `loading="lazy"` on `<img>`.
- Even better: Use a custom `<FadeInImage>` that only sets `src` when `inView` (though `react-window` handles visibility well).
- **Crucial:** Set explicit `width` and `height` on images to prevent layout shifts.
## π Usage Pattern
```jsx
import { FixedSizeGrid } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
// ... inside your component
<div style={{ height: '100vh', width: '100%' }}>
<AutoSizer>
{({ height, width }) => (
<FixedSizeGrid
columnCount={4}
columnWidth={width / 4}
height={height}
rowCount={Math.ceil(items.length / 4)}
rowHeight={300} // Fixed height!
width={width}
overscanRowCount={3} // CRITICAL FOR TV
>
{({ columnIndex, rowIndex, style }) => {
const index = rowIndex * 4 + columnIndex;
if (index >= items.length) return null;
return (
<div style={style}>
<MovieCard
item={items[index]}
isFocused={index === focusedIndex}
/>
</div>
);
}}
</FixedSizeGrid>
)}
</AutoSizer>
</div>
```
## β οΈ Anti-Patterns
- **β `react-virtualized` (standard)**: Too heavy for some older TVs.
- **β Inline Functions**: `itemData={items}` is fine, but avoid defining the render function inline if possible (hoist it).
- **β Complex Cards**: Keep the item component simple. Heavy CSS effects (blur, shadows) on 20 items at once will drop frames.
This skill specializes in high-performance list virtualization for Android TV apps built with React. It focuses on delivering smooth 60FPS scrolling on low-end TV boxes by using lightweight virtualization, TV-specific optimizations, and predictable layout strategies. The guidance targets movie and episode grids where large datasets and fast directional navigation are common.
It uses react-window with react-virtualized-auto-sizer to render only visible cells and a small overscan buffer, minimizing DOM nodes and paint work. The approach enforces fixed item sizes, lazy image loading, and a focus-index-driven navigation model so focus remains stable even when items unmount off-screen. Combined, these tactics avoid layout thrashing and preserve responsiveness on constrained TV hardware.
Why not use react-virtualized or variable sizes?
react-virtualized is heavier and variable sizes force expensive layout work per frame, which kills performance on older TV hardware. Fixed-size virtualization is far more predictable and efficient.
How do I prevent focus from being lost when items unmount?
Donβt rely on DOM focus. Track focusedIndex in state via your TV navigation hook and re-apply focus when the item remounts or render it with an isFocused prop so it appears focused without requiring document.activeElement.