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
- suleyman/fullstack-agent-skillstheir own version, first seen Oct 6, 2026
- CrowPus/Zelyqtheir own version, first seen Aug 22, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- CrowPus/Zelyq★ 10same name
- 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.
- prompt-betterSkill
- rn-reviewSkill
- write-better-error-messagesSkill
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
- Very specific: line 20
If package.json is unavailable, load only the references directly relevant to the current task.
- Very specific: line 121
Never use index as keyExtractor — breaks recycler identity
- Very specific: line 131
Do not write runOnJS — use scheduleOnRN for calling JS from a worklet
- Very specific: line 140
useController: always in RN — register() doesn't work on TextInput - Very specific: line 195
SDK 56: import from expo-router directly — @react-navigation/native no longer available via expo-router's deps
≈ “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.
Files
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-form | references/forms.md |
reanimated | references/animations.md |
zustand or @tanstack/react-query | references/state-and-effects.md |
@shopify/flash-list | references/performance.md |
nativewind | references/nativewind.md |
If package.json is unavailable, load only the references directly relevant to the current task.
Ground Rules
- 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={...}>
StyleSheet.absoluteFillObjectremoved in RN 0.85 — useStyleSheet.absoluteFill- Expo Router SDK 56: forks React Navigation —
@react-navigation/*is no longer a peer dep ofexpo-router. Run the codemod after upgrading. - React 19 in SDK 56 —
use(),useTransition,useOptimistic.refis now a plain prop; do not writeforwardReffor new components. - React Compiler (default in SDK 54+ new projects) — do not write
useMemooruseCallback. The compiler handles memoization. Writing them is wrong by default, not a safe default. Only exception: profiler shows the component bailing out (useMemoCacheabsent in fiber tree) AND the computation is measurably expensive (>1ms or visible jank). - Reanimated 4 (requires New Architecture) —
scheduleOnRNreplacesrunOnJS. Seereferences/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 notSee 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}
/>renderItemmust be a stable reference — no inline arrow functionsrecyclingKey={item.id}on everyexpo-imageinside list items- Never use
indexaskeyExtractor— breaks recycler identity MasonryFlashListno longer exists — use<FlashList masonry numColumns={2} />
5. Animations — Reanimated 4
See references/animations.md. Core rules:
transform+opacityare always safe; layout props (width,height, etc.) can also animate natively in RN 0.85+ via Shared Animation Backend- Scroll position →
useSharedValue, neveruseState - Do not write
runOnJS— usescheduleOnRNfor 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:
useControlleralways in RN —register()doesn't work onTextInput- Zod v4: format validators are now top-level:
z.email(),z.url(),z.uuid()(notz.string().email()) z.coerce.number()for numeric TextInput values (TextInput always returns strings)formStateis 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
| Use | Not |
expo-audio | expo-av for audio |
expo-video | expo-av for video |
expo-image | react-native Image, FastImage |
expo-image source="sf:name" | expo-symbols, @expo/vector-icons |
react-native-safe-area-context | SafeAreaView 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-store | AsyncStorage for secrets |
Pressable | TouchableOpacity, TouchableHighlight |
StyleSheet.absoluteFill | StyleSheet.absoluteFillObject (removed in RN 0.85) |
9. SDK / RN Breaking Changes by Version
| Version | Key code-level changes |
| RN 0.85 | StyleSheet.absoluteFillObject removed; layout props (width, height, etc.) now animatable with native driver via Shared Animation Backend |
| Expo SDK 56 | RN 0.85.2 + React 19.2.3; Expo Router forks React Navigation (codemod required) |
| Expo SDK 55 | expo-av → expo-audio + expo-video |
| Expo SDK 54 | React 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-routerdirectly —@react-navigation/nativeno longer available viaexpo-router's deps - Route files only in
app/; components/hooks/types incomponents//hooks//types/ - kebab-case filenames;
_layout.tsxin 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.
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.

