agentsclimarketplace

Performance

Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/performance

Use when optimizing app performance -- TTI targets, FlashList, FPS measurement, Hermes, React Compiler, bundle analysis, memo patterns, avoiding re-renders.From its SKILL.md

Install
npx -y skills add Shankulkarni/react-native-ai-plugin --skill performance

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.6 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

Performance

TTI Targets (Time to Interactive)

TTIRating
<2sExcellent
2--4sAcceptable
>4sPoor -- must fix

JS Bundle load target: <500ms. Native init target: <500ms.

FPS Targets

FPSState
55--60Smooth -- target
45--55Slight stutter -- investigate
30--45Noticeable -- fix before release
<30Critical

UI FPS drop = native rendering issue (layout properties animated, heavy views)
JS FPS drop = JS thread blocked (heavy computation, too many re-renders)

Lists -- FlashList over ScrollView

// ❌ ScrollView -- renders ALL items upfront, freezes on long lists
<ScrollView>
  {posts.map(p => <PostCard key={p.id} {...p} />)}
</ScrollView>

// ✅ FlashList -- virtualizes, renders only visible items
import { FlashList } from '@shopify/flash-list'

<FlashList
  data={posts}
  renderItem={({ item }) => <PostCard id={item.id} title={item.title} />}
  estimatedItemSize={88}
  keyExtractor={item => item.id}
  onEndReached={fetchNextPage}
  onEndReachedThreshold={0.5}
  ListEmptyComponent={<EmptyState />}
  ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
/>

List Memoization Rules

// ❌ Object prop -- breaks memo, re-renders every time
<FlashList renderItem={({ item }) => <PostCard data={item} onPress={handlePress} />} />

// ✅ Primitive props -- shallow comparison works
<FlashList renderItem={({ item }) => (
  <PostCard id={item.id} title={item.title} onPress={handlePress} />
)} />

// ✅ Memoized item
const PostCard = memo(({ id, title, onPress }: PostCardProps) => ...)

// ✅ Hoisted callback -- stable reference
const handlePress = useCallback((id: string) => router.push({ pathname: '/(app)/[id]', params: { id } }), [])

// ✅ Memoized data
const filteredPosts = useMemo(() => posts.filter(p => p.active), [posts])

Heterogeneous Lists

type FeedItem = { type: 'post' | 'ad' | 'story' } & (PostItem | AdItem | StoryItem)

<FlashList
  data={feed}
  getItemType={item => item.type}
  estimatedItemSize={88}
  renderItem={({ item }) => {
    if (item.type === 'post') return <PostCard {...item} />
    if (item.type === 'ad') return <AdCard {...item} />
    return <StoryCard {...item} />
  }}
/>

Avoid Re-renders

// ❌ Inline object -- new reference every render
<Component style={{ color: 'red' }} options={{ timeout: 1000 }} />

// ✅ Outside component or useMemo
const STYLE = { color: 'red' as const }
const OPTIONS = { timeout: 1000 }

// ❌ Inline function -- new reference every render
<FlatList renderItem={({ item }) => <Item {...item} onPress={() => handlePress(item.id)} />} />

// ✅ useCallback
const renderItem = useCallback(({ item }) => <Item {...item} onPress={handlePress} />, [handlePress])

React Compiler (RN 0.76+, Expo SDK 52+)

Automatically removes memo, useCallback, useMemo where safe.

# Check if your code is compiler-ready
bunx react-compiler-healthcheck@latest

Write compiler-friendly code:

// ✅ Destructure functions at render top
function Screen({ onPress, onChange }: Props) {
  // Compiler can optimize these
  const handlePress = () => onPress()
  const handleChange = (v: string) => onChange(v)
}

// ❌ Dot-access in JSX
<Pressable onPress={() => props.onPress()} />

Hermes

Enabled by default in Expo SDK 52+. Hermes benefits:

  • Faster startup (bytecode pre-compilation)
  • Lower memory usage
  • Better GC

Ensure Hermes is enabled in app.json:

{ "expo": { "jsEngine": "hermes" } }

Animate GPU Properties Only

// ❌ Causes layout recalculation
width.value = withSpring(200)
marginTop.value = withTiming(20)

// ✅ GPU only
transform: [{ scale: scaleValue }]
opacity: opacityValue

Intl at Module Scope

// ❌ Created in render -- expensive
function formatDate(date: Date) {
  return new Intl.DateTimeFormat('en-US', { month: 'short' }).format(date)
}

// ✅ Created once at module level
const dateFormatter = new Intl.DateTimeFormat('en-US', { month: 'short' })
function formatDate(date: Date) { return dateFormatter.format(date) }

Bundle Analysis

# Expo Atlas (most accurate for Expo)
EXPO_UNSTABLE_ATLAS=true bunx expo export
# Open .expo/atlas.jsonl in Atlas viewer

# Source map explorer
bunx source-map-explorer dist/index.js

Common bundle issues:

  • Barrel exports (import { x } from 'big-library' loads everything)
  • Moment.js (use dayjs instead)
  • Duplicate packages (check bun why <package>)

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.