home / skills / dbobkov245-source / pwa-torserve / perf-virtual-lists

perf-virtual-lists skill

/skills/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-lists

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.1 KB
---
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.

Overview

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.

How this skill works

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.

When to use it

  • Large collections of posters or tiles (hundreds to thousands) in Android TV UIs.
  • Layouts where users navigate quickly via D-pad (holding Down/Up).
  • Cases where consistent 60FPS is required on low-memory TV boxes (e.g., 1GB RAM).
  • Grid or list layouts where item dimensions can be fixed ahead of time.
  • When image-heavy cards risk causing layout shifts during scroll.

Best practices

  • Prefer FixedSizeList or FixedSizeGrid; avoid variable-size virtualization to prevent per-frame layout recalculation.
  • Set overscanRowCount/overscanCount to at least 2–3 rows for TV to avoid blank regions during fast scrolls.
  • Manage focus by tracking a focusedIndex in JS (useTVNavigation pattern) rather than relying on document.activeElement.
  • Give images explicit width and height, use loading="lazy" or a FadeInImage that sets src only when inView.
  • Keep item components lightweight: avoid heavy CSS effects and inline render functions; hoist renderers where possible.

Example use cases

  • A movie grid showing thousands of posters where users navigate with a remote and expect instant responsiveness.
  • An episode list with fixed-height rows and fast vertical paging where overscan prevents blank frames.
  • A home screen carousel of recommended content using FixedSizeGrid to maintain smooth D-pad scrolling.
  • A NAS media browser with image-heavy tiles that uses lazy loading and fixed sizes to avoid layout shifts.

FAQ

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.