Skip to content

list-performance

<!-- Loaded by react-native-engineer when task involves lists, FlatList, FlashList, LegendList, virtualization, renderItem, scroll performance -->

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

<!-- Loaded by react-native-engineer when task involves lists, FlatList, FlashList, LegendList, virtualization, renderItem, scroll performance -->

Agent definition

list-performance.md

List Performance Reference

<!-- Loaded by react-native-engineer when task involves lists, FlatList, FlashList, LegendList, virtualization, renderItem, scroll performance -->

Use a Virtualizer for Any Scrollable List

**Impact:** CRITICAL — only visible items rendered, reduces memory and mount time

**Instead of:**

<ScrollView>
  {items.map((item) => <ItemCard key={item.id} item={item} />)}
</ScrollView>

**Use:**

import { LegendList } from '@legendapp/list'

function Feed({ items }: { items: Item[] }) {
  return (
    <LegendList
      data={items}
      renderItem={({ item }) => <ItemCard item={item} />}
      keyExtractor={(item) => item.id}
      estimatedItemSize={80}
    />
  )
}

---

Keep Stable Object References — Transform Inside Items

**Impact:** CRITICAL — virtualization skips re-renders only when references are stable

Mapping/filtering before passing to list creates new references every render, forcing full re-renders on every state update.

**Instead of:**

const domains = tlds.map((tld) => ({
  domain: `${keyword}.${tld.name}`,
  price: tld.price,
}))
return <LegendList data={domains} ... />

**Use:**

<LegendList
  data={tlds}  // stable references
  renderItem={({ item }) => <DomainItem tld={item} />}
/>

function DomainItem({ tld }: { tld: Tld }) {
  const domain = useKeywordStore((s) => s.keyword + '.' + tld.name)
  return <Text>{domain}</Text>
}

New sorted arrays are fine if inner object references stay the same: `tlds.toSorted(...)`.

---

Pass Primitives and Stable References to List Items

**Impact:** HIGH — prevents re-renders of memoized items

Inline objects/styles in `renderItem` create new references every render, breaking `memo()`.

**Instead of:**

renderItem={({ item }) => (
  <UserRow
    user={{ id: item.id, name: item.name }}
    style={{ backgroundColor: item.isActive ? 'green' : 'gray' }}
  />
)}

**Use:**

renderItem={({ item }) => (
  <UserRow id={item.id} name={item.name} isActive={item.isActive} />
)}

// Or hoist static styles to module scope
const activeStyle = { backgroundColor: 'green' }
const inactiveStyle = { backgroundColor: 'gray' }

---

Pass Primitives for Effective Memoization

**Impact:** HIGH — enables shallow comparison in memo()

**Instead of:**

const UserRow = memo(function UserRow({ user }: { user: User }) {
  return <Text>{user.name}</Text>
})
renderItem={({ item }) => <UserRow user={item} />}

**Use:**

const UserRow = memo(function UserRow({ id, name }: { id: string; name: string }) {
  return <Text>{name}</Text>
})
renderItem={({ item }) => <UserRow id={item.id} name={item.name} />}

---

Hoist Callbacks to the Root — Items Call with ID

**Impact:** HIGH — single stable callback vs new closure per item

**Instead of:**

renderItem={({ item }) => {
  const onPress = () => handlePress(item.id)
  return <Item item={item} onPress={onPress} />
}}

**Use:**

const onPress = useCallback((id: string) => handlePress(id), [handlePress])
renderItem={({ item }) => <Item item={item} onPress={onPress} />}

---

Keep List Items Lightweight

**Impact:** HIGH — reduces per-item render time during scroll

**Instead of:**

function ProductRow({ id }: { id: string }) {
  const { data: product } = useQuery(['product', id], () => fetchProduct(id))
  const theme = useContext(ThemeContext)
  const cart = useContext(CartContext)
  const recs = useMemo(() => computeRecommendations(product), [product])
  return <View>{/* ... */}</View>
}

**Use:**

function ProductList() {
  const { data: products } = useQuery(['products'], fetchProducts)
  return (
    <LegendList
      data={products}
      renderItem={({ item }) => (
        <ProductRow name={item.name} price={item.price} imageUrl={item.image} />
      )}
    />
  )
}

function ProductRow({ name, price, imageUrl }: Props) {
  const inCart = useCartStore((s) => s.items.has(id))
  return <View>{/* ... */}</View>
}

No queries in items, no expensive computations, prefer Zustand selectors over Context.

---

Use Item Types for Heterogeneous Lists

**Impact:** HIGH — efficient recycling, prevents layout thrashing

<LegendList
  data={items}
  keyExtractor={(item) => item.id}
  getItemType={(item) => item.type}
  getEstimatedItemSize={(index, item, itemType) => {
    switch (itemType) {
      case 'header': return 48
      case 'message': return 72
      case 'image': return 300
      default: return 72
    }
  }}
  renderItem={({ item }) => {
    switch (item.type) {
      case 'header': return <SectionHeader title={item.title} />
      case 'message': return <MessageRow text={item.text} />
      case 'image': return <ImageRow url={item.url} />
    }
  }}
  recycleItems
/>

---

Use Compressed, Appropriately-Sized Images in Lists

**Impact:** HIGH — reduces memory; thumbnails load at display dimensions

**Instead of:**

<Image source={{ uri: product.imageUrl }} style={{ width: 100, height: 100 }} />

**Use:**

import { Image } from 'expo-image'

function ProductItem({ product }: { product: Product }) {
  const thumbnailUrl = `${product.imageUrl}?w=200&h=200&fit=cover`  // 2x retina

  return (
    <Image
      source={{ uri: thumbnailUrl }}
      style={{ width: 100, height: 100 }}
      contentFit="cover"
      recyclingKey={product.id}
    />
  )
}

Use `expo-image` for list images — memory-efficient caching and `recyclingKey` for recycled cells.

Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked