Claude
Skills
Sign in
Back

virtualization

Included with Lifetime
$97 forever

MUI list virtualization with react-window, react-virtuoso, virtualized Autocomplete, and large dataset rendering patterns

Web Dev

What this skill does


# MUI Virtualization

## Why Virtualize

Rendering 1000+ DOM nodes causes:
- **Layout thrashing** — the browser recalculates layout for every node on scroll
- **Memory pressure** — each MUI ListItem creates 3-5 DOM elements (ripple, text, icon wrappers)
- **Initial paint delay** — 10,000 items can take 2-4 seconds to mount

The fix: render only the visible items plus a small overscan buffer. Libraries like
react-window and react-virtuoso handle the math; you supply the row renderer.

**Rule of thumb:** virtualize any list with more than 100 items, or any list where
item count is unbounded (API-driven).

---

## react-window with MUI

Install: `npm install react-window @types/react-window`

### FixedSizeList with MUI ListItem

Use when every row has the same pixel height.

```tsx
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';

interface User {
  id: string;
  name: string;
  email: string;
  avatarUrl: string;
}

interface VirtualUserListProps {
  users: User[];
  height?: number;
}

function renderRow(users: User[]) {
  return function Row({ index, style }: ListChildComponentProps) {
    const user = users[index];
    return (
      <ListItem
        style={style}        // react-window positions via inline style
        key={user.id}
        component="div"      // avoid nested <li> issues
        disablePadding
        sx={{ borderBottom: 1, borderColor: 'divider' }}
      >
        <ListItemAvatar>
          <Avatar src={user.avatarUrl} alt={user.name} />
        </ListItemAvatar>
        <ListItemText primary={user.name} secondary={user.email} />
      </ListItem>
    );
  };
}

export function VirtualUserList({ users, height = 400 }: VirtualUserListProps) {
  return (
    <Box sx={{ width: '100%', height, bgcolor: 'background.paper' }}>
      <FixedSizeList
        height={height}
        width="100%"
        itemSize={72}           // must match actual row height
        itemCount={users.length}
        overscanCount={5}       // render 5 extra rows above/below viewport
      >
        {renderRow(users)}
      </FixedSizeList>
    </Box>
  );
}
```

### VariableSizeList with MUI ListItem

Use when rows have different heights (e.g., multi-line text, expandable content).

```tsx
import { VariableSizeList } from 'react-window';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import { useRef, useCallback } from 'react';

interface Message {
  id: string;
  sender: string;
  body: string;
}

interface VirtualMessageListProps {
  messages: Message[];
  height?: number;
}

// Estimate height based on text length; refine with measureRef if needed
function getItemSize(messages: Message[]) {
  return (index: number): number => {
    const body = messages[index].body;
    if (body.length < 80) return 56;
    if (body.length < 200) return 80;
    return 120;
  };
}

export function VirtualMessageList({ messages, height = 500 }: VirtualMessageListProps) {
  const listRef = useRef<VariableSizeList>(null);

  // Call this after data changes to recalculate sizes
  const resetSizes = useCallback(() => {
    listRef.current?.resetAfterIndex(0, true);
  }, []);

  return (
    <VariableSizeList
      ref={listRef}
      height={height}
      width="100%"
      itemCount={messages.length}
      itemSize={getItemSize(messages)}
      overscanCount={3}
    >
      {({ index, style }) => {
        const msg = messages[index];
        return (
          <ListItem style={style} component="div" alignItems="flex-start">
            <ListItemText
              primary={msg.sender}
              secondary={msg.body}
              secondaryTypographyProps={{
                sx: {
                  display: '-webkit-box',
                  WebkitLineClamp: 3,
                  WebkitBoxOrient: 'vertical',
                  overflow: 'hidden',
                },
              }}
            />
          </ListItem>
        );
      }}
    </VariableSizeList>
  );
}
```

**Key gotcha:** When data changes, call `listRef.current.resetAfterIndex(0, true)` to
force VariableSizeList to recalculate cached sizes.

---

## react-virtuoso with MUI

Install: `npm install react-virtuoso`

Virtuoso handles variable-height items automatically (no `itemSize` function needed)
and supports grouped lists, headers, footers, and scroll-to-index out of the box.

### Basic Virtuoso with MUI List Components

```tsx
import { Virtuoso } from 'react-virtuoso';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import FolderIcon from '@mui/icons-material/Folder';
import { forwardRef, type ReactElement } from 'react';

interface FileItem {
  id: string;
  name: string;
  size: string;
  onClick: () => void;
}

// Virtuoso needs the list container and item wrappers as custom components
const MuiListComponent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  (props, ref) => <List component="div" ref={ref} {...props} />,
);
MuiListComponent.displayName = 'MuiListComponent';

const MuiItemComponent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  (props, ref) => <div ref={ref} {...props} />,
);
MuiItemComponent.displayName = 'MuiItemComponent';

interface VirtualFileListProps {
  files: FileItem[];
  height?: number;
}

export function VirtualFileList({ files, height = 600 }: VirtualFileListProps): ReactElement {
  return (
    <Virtuoso
      style={{ height }}
      totalCount={files.length}
      components={{
        List: MuiListComponent,
        Item: MuiItemComponent,
      }}
      itemContent={(index) => {
        const file = files[index];
        return (
          <ListItemButton onClick={file.onClick}>
            <ListItemIcon>
              <FolderIcon />
            </ListItemIcon>
            <ListItemText primary={file.name} secondary={file.size} />
          </ListItemButton>
        );
      }}
    />
  );
}
```

### Grouped Virtuoso with MUI Subheaders

```tsx
import { GroupedVirtuoso } from 'react-virtuoso';
import ListSubheader from '@mui/material/ListSubheader';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';

interface GroupedData {
  groups: string[];
  groupCounts: number[];
  items: Array<{ label: string; value: string }>;
}

export function GroupedVirtualList({ groups, groupCounts, items }: GroupedData) {
  return (
    <GroupedVirtuoso
      style={{ height: 500 }}
      groupCounts={groupCounts}
      groupContent={(index) => (
        <ListSubheader sx={{ bgcolor: 'background.paper', fontWeight: 700 }}>
          {groups[index]}
        </ListSubheader>
      )}
      itemContent={(index) => (
        <ListItem>
          <ListItemText primary={items[index].label} secondary={items[index].value} />
        </ListItem>
      )}
    />
  );
}
```

---

## Virtualized Autocomplete

MUI's official pattern for Autocomplete with thousands of options. The key is
overriding the `ListboxComponent` prop with a virtualized list.

### Using react-window

```tsx
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Popper from '@mui/material/Popper';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import {
  forwardRef,
  useRef,
  createContext,
  useContext,
  type HTMLAttributes,
  type ReactElement,
} from 'react';

// Context to pass props from Autocomplete to the virtualized list
const OuterElementContext =

Related in Web Dev