agentleFS
sagawrrsagawrr/skills/expo-react-native/SKILL.md

expo-react-native

Skill0 starsChanged 5 months agoMITView on GitHub

Battle-tested code patterns pinned to Expo SDK 56 (RN 0.85.2, React 19.2.3, Hermes v1), Reanimated 4, FlashList v2, NativeWind v4, Zustand v5, Zod v4, React Hook Form v7. Use when writing, reviewing, or refactoring React Native component code. Covers hooks, state, animations, lists, images, forms, and styling — not build config or tooling.

  • Reads credentials

2 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions2 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 2

    1. CrowPus/Zelyq★ 10same name
    2. suleyman/fullstack-agent-skills★ 2same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository3 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 0 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If package.json is unavailable, load only the references directly relevant to the current task.

    line 20
  2. Very specific:

    Never use index as keyExtractor — breaks recycler identity

    line 121
  3. Very specific:

    Do not write runOnJS — use scheduleOnRN for calling JS from a worklet

    line 131
  4. Very specific:

    useController: always in RN — register() doesn't work on TextInput

    line 140
  5. Very specific:

    SDK 56: import from expo-router directly — @react-navigation/native no longer available via expo-router's deps

    line 195

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Expo / React Native — Hardened Patterns

Reference Loading

Before reading any reference file, read package.json and use this table to decide which ones to load. Load only what matches — skip the rest.

If package.json contains…Load
react-hook-formreferences/forms.md
reanimatedreferences/animations.md
zustand or @tanstack/react-queryreferences/state-and-effects.md
@shopify/flash-listreferences/performance.md
nativewindreferences/nativewind.md

If package.json is unavailable, load only the references directly relevant to the current task.


Ground Rules

  1. New Architecture is default (RN 0.76+, Expo SDK 53+). Two silent breakage patterns:
    • Auto state-batching drops intermediate values — components relying on them break silently
    • View flattening makes refs null — add collapsable={false} to every <View ref={...}>
  2. StyleSheet.absoluteFillObject removed in RN 0.85 — use StyleSheet.absoluteFill
  3. Expo Router SDK 56: forks React Navigation — @react-navigation/* is no longer a peer dep of expo-router. Run the codemod after upgrading.
  4. React 19 in SDK 56 — use(), useTransition, useOptimistic. ref is now a plain prop; do not write forwardRef for new components.
  5. React Compiler (default in SDK 54+ new projects) — do not write useMemo or useCallback. The compiler handles memoization. Writing them is wrong by default, not a safe default. Only exception: profiler shows the component bailing out (useMemoCache absent in fiber tree) AND the computation is measurably expensive (>1ms or visible jank).
  6. Reanimated 4 (requires New Architecture) — scheduleOnRN replaces runOnJS. See references/animations.md.

1. useEffect — 3 Legitimate Uses

Full replacement map in references/state-and-effects.md.

// ❌ derives state in an effect — extra render, stale frame
useEffect(() => setFiltered(items.filter(fn)), [items, fn]);

// ✅ compute during render
const filtered = items.filter(fn);

// ❌ user event side effect in an effect
useEffect(() => { if (submitted) analytics.track('submitted'); }, [submitted]);

// ✅ in the handler
async function handleSubmit() { await submit(data); analytics.track('submitted'); }

// use() — promise must be stable (created outside render)
function Profile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise); // wrap parent in <Suspense> + <ErrorBoundary>
  return <Text>{user.name}</Text>;
}

2. State — Zustand v5

// Named imports only in v5 — no default exports
import { create } from 'zustand';
import { useShallow } from 'zustand/shallow';

// ✅ Selector — re-renders only when subscribed slice changes
const count = useStore((s) => s.count);

// ✅ useShallow — when selector returns object/array (prevents re-render on same shallow values)
const { count, name } = useStore(useShallow((s) => ({ count: s.count, name: s.name })));

// Context for stable values (auth, theme) — use() in React 19
const user = use(AuthContext); // works in conditionals; useContext does not

See references/state-and-effects.md for full Zustand v5 patterns, slices, persist.


3. React 19

const [isPending, startTransition] = useTransition();
startTransition(() => setSearchQuery(text)); // interruptible

const [likes, addOptimistic] = useOptimistic(serverLikes, (cur, inc) => cur + inc);
async function handleLike() { addOptimistic(1); await likePost(id); }

// ref is a plain prop in React 19
function Input({ ref, ...props }: Props & { ref?: Ref<TextInput> }) {
  return <TextInput ref={ref} {...props} />;
}

4. Lists — FlashList v2

import { FlashList } from '@shopify/flash-list';

<FlashList
  data={items}
  renderItem={({ item }) => <ItemRow item={item} />}
  keyExtractor={item => item.id}
  getItemType={item => item.type}   // required for heterogeneous lists
  onEndReached={fetchNextPage}
  onEndReachedThreshold={0.5}
  // DO NOT add estimatedItemSize / estimatedListSize / estimatedFirstItemOffset — removed in v2, auto-handled internally
  // masonry layout (replaces standalone MasonryFlashList):
  // masonry numColumns={2}
/>
  • renderItem must be a stable reference — no inline arrow functions
  • recyclingKey={item.id} on every expo-image inside list items
  • Never use index as keyExtractor — breaks recycler identity
  • MasonryFlashList no longer exists — use <FlashList masonry numColumns={2} />

5. Animations — Reanimated 4

See references/animations.md. Core rules:

  • transform + opacity are always safe; layout props (width, height, etc.) can also animate natively in RN 0.85+ via Shared Animation Backend
  • Scroll position → useSharedValue, never useState
  • Do not write runOnJS — use scheduleOnRN for calling JS from a worklet
  • CSS transitions (single): style={{ transitionProperty: 'opacity', transitionDuration: '300ms', opacity: isVisible ? 1 : 0 }}
  • CSS transitions (multi): transitionProperty: ['opacity', 'transform'], transitionDuration: ['300ms', '200ms'] — arrays, not comma strings

6. Forms — RHF v7 + Zod v4

See references/forms.md. Core rules:

  • useController always in RN — register() doesn't work on TextInput
  • Zod v4: format validators are now top-level: z.email(), z.url(), z.uuid() (not z.string().email())
  • z.coerce.number() for numeric TextInput values (TextInput always returns strings)
  • formState is a Proxy — read sub-properties unconditionally, never conditionally

7. Images

import { Image } from 'expo-image';
<Image source={{ uri }} placeholder={{ blurhash }} contentFit="cover" transition={200} recyclingKey={String(item.id)} />
{/* recyclingKey requires string — coerce numeric IDs with String() */}

8. Library Preferences

UseNot
expo-audioexpo-av for audio
expo-videoexpo-av for video
expo-imagereact-native Image, FastImage
expo-image source="sf:name"expo-symbols, @expo/vector-icons
react-native-safe-area-contextSafeAreaView from react-native
Platform.OS (Metro tree-shakes platform branches)process.env.EXPO_OS (runtime only, no tree-shaking)
React.use(Context)useContext(Context)
useWindowDimensions()Dimensions.get()
expo-secure-storeAsyncStorage for secrets
PressableTouchableOpacity, TouchableHighlight
StyleSheet.absoluteFillStyleSheet.absoluteFillObject (removed in RN 0.85)

9. SDK / RN Breaking Changes by Version

VersionKey code-level changes
RN 0.85StyleSheet.absoluteFillObject removed; layout props (width, height, etc.) now animatable with native driver via Shared Animation Backend
Expo SDK 56RN 0.85.2 + React 19.2.3; Expo Router forks React Navigation (codemod required)
Expo SDK 55expo-av → expo-audio + expo-video
Expo SDK 54React Compiler enabled by default in new projects; react-native-worklets required for Reanimated

10. Expo Router (SDK 56)

app/
  _layout.tsx          root layout — providers only
  (tabs)/_layout.tsx   NativeTabs
  (modal)/sheet.tsx    presentation: formSheet
  • SDK 56: import from expo-router directly — @react-navigation/native no longer available via expo-router's deps
  • Route files only in app/; components/hooks/types in components//hooks//types/
  • kebab-case filenames; _layout.tsx in every route group
<Link href="/detail">View</Link>

import { NativeTabs } from 'expo-router/unstable-native-tabs'; // uses native system tab bar

<Stack.Screen options={{ presentation: 'formSheet', sheetAllowedDetents: [0.5, 1.0] }} />

11. Styling

<View style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.12)' }} />  // New Architecture (RN 0.76+) — do not write shadow*/elevation props, silently ignored on New Architecture
<ScrollView contentContainerStyle={{ padding: 16, gap: 12 }}>
<ScrollView contentInsetAdjustmentBehavior="automatic" />
<View style={{ borderRadius: 12, borderCurve: 'continuous' }} />
<Text style={{ fontVariant: ['tabular-nums'] }}>{price}</Text>
<Text selectable>{address}</Text>

For NativeWind v4: references/nativewind.md.


Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.