Essential Libraries Basics
10 examples to understand how we pick, pin, and sunset dependencies on mobile - 7 basic and 3 intermediate. These are the team defaults for Expo SDK 57 apps; deeper cookbooks for each library follow in this section.
Search across all documentation pages
10 examples to understand how we pick, pin, and sunset dependencies on mobile - 7 basic and 3 intermediate. These are the team defaults for Expo SDK 57 apps; deeper cookbooks for each library follow in this section.
Start from a pinned SDK 57 project. Essential libraries are curated defaults - not every npm package that solves a problem belongs in a shipping app.
npx create-expo-app@latest LibBasics --template default@sdk-57
cd LibBasics
npx expo install @tanstack/react-query @react-native-community/netinfo zustand
npx expo-doctorConfirm the baseline pin before adding native modules:
{
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0"
}
}Tooling: These examples target Expo SDK 57 (
expo~57.0.4), React Native 0.86.0, and React 19.2.3. Native modules require a development build or EAS build - not stock Expo Go alone when the package ships custom native code.
Mobile dependency decisions start with what kind of state or I/O you need - not trending repos.
┌────────────────────┬─────────────────────────┬──────────────────────────┐
│ Problem │ Default library │ Do not use for │
├────────────────────┼─────────────────────────┼──────────────────────────┤
│ API / server cache │ @tanstack/react-query │ Cart badge, theme toggle │
│ Global client UI │ zustand │ Product catalog rows │
│ Fast local KV │ react-native-mmkv │ Relational offline data │
│ Long lists │ @shopify/flash-list │ 8-row settings screen │
│ Gestures / motion │ Reanimated 4 + RNGH │ One-shot 200ms fade │
│ Crash reporting │ @sentry/react-native │ Console.log in prod │
└────────────────────┴─────────────────────────┴──────────────────────────┘npx expo install resolves versions; fewer native build surprisesRelated: State Management Basics - server vs client split
npx expo install - Not Raw npmExpo maintains a compatibility matrix per SDK. Hand-pinning react-native-reanimated@latest breaks EAS builds when the native ABI does not match RN 0.86.
# Correct - resolves SDK 57-compatible version
npx expo install react-native-reanimated react-native-gesture-handler
# Risky for native modules - may install incompatible semver
npm install react-native-reanimated# Verify after any dependency change
npx expo-doctornpx expo install writes the tilde-pinned range Expo tested against your SDKzod, @tanstack/react-query, zustand) can use npm install - no native bridgeexpo-doctor on release and nightly CI - catches skew before eas buildRelated: Expo SDK Basics - when
npm installis acceptable
Every library with native code pulls in autolinking, config plugins, and store binary size. Read the install docs before merging.
# After adding a candidate library
npx expo install react-native-mmkv
npx expo prebuild --dry-run # surfaces config plugin changes
npx expo-doctorChecklist before merge:
| Question | Pass | Fail → defer or find alternative |
|---|---|---|
| Works in Expo managed workflow? | Yes | Requires bare fork |
Needs config plugin in app.config? | Documented | Surprise native diff |
| Requires dev client rebuild? | Planned in sprint | Blocked on Expo Go-only QA |
| Duplicates existing library? | No overlap | Consolidate first |
expo prebuild --dry-run previews native project changes without writing ios/ / android/Related: Dead Code & Dependency Analysis - keep the graph lean
Pin the essential stack in one PR during project bootstrap so every feature branch inherits the same versions.
{
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0",
"@tanstack/react-query": "^5.0.0",
"zustand": "^5.0.0",
"react-native-reanimated": "~4.0.0",
"react-native-gesture-handler": "~2.28.0",
"@sentry/react-native": "^6.0.0"
}
}Native list/storage pins (@shopify/flash-list, react-native-mmkv, @react-native-community/netinfo) are written by npx expo install - do not hand-edit those ranges.
npx expo install @tanstack/react-query @react-native-community/netinfo zustand \
@shopify/flash-list react-native-mmkv react-native-reanimated \
react-native-gesture-handler @sentry/react-nativenpx expo install set exact compatible ranges - do not hand-edit native pinspackage.json, not tribal knowledgeThe most expensive library mistake is duplicating API data in Zustand or Context when TanStack Query already owns it.
// ❌ Anti-pattern - manual fetch + global store for server data
const useProductStore = create((set) => ({
products: [] as Product[],
load: async () => {
const res = await fetch("/products");
set({ products: await res.json() });
},
}));
// ✅ Pattern - Query owns server cache; Zustand owns UI-only flags
const { data: products } = useQuery({ queryKey: ["products"], queryFn: fetchProducts });
const filter = useFilterStore((s) => s.category); // client-onlyexpo-secure-storeRelated: TanStack Query - full server-state deep dive
Not every app needs react-native-mmkv on day one. Start with AsyncStorage; upgrade when profiling proves sync reads matter.
Storage pick order:
1. In-memory React state / Query cache → ephemeral
2. @react-native-async-storage/async-storage → small JSON, boot hydration
3. react-native-mmkv → hot-path sync reads every frame
4. expo-sqlite → relational rows, migrations, queries
5. expo-secure-store → tokens, secretsnpx expo install @react-native-async-storage/async-storage
# Add MMKV only after React DevTools Profiler shows storage await on critical path
npx expo install react-native-mmkvawait getItem on every render causes jankRelated: react-native-mmkv - fast KV cookbook
@shopify/flash-list is a performance upgrade - not a day-one requirement for every screen.
// Short settings list - FlatList is fine
import { FlatList } from "react-native";
// Image-heavy feed with scroll jank on Android - migrate
import { FlashList } from "@shopify/flash-list";Migration trigger checklist:
windowSize tuning on FlatList did not fix blank flashesnpx expo install @shopify/flash-listFlatList preemptively - estimate tuning has a costRelated: FlashList vs FlatList - trade-offs before adopting
Sunsetting is a planned removal - not letting a package rot in package.json until the upgrade explodes.
# 1. Confirm zero imports
npx knip
npx depcheck
# 2. Remove package and lockfile entry
npm uninstall legacy-state-library
# 3. Remove native config plugin from app.config.ts if present
# 4. Rebuild dev client - autolinking must drop the native module
eas build --profile development --platform all
# 5. Verify on device
npx expo-doctor// app.config.ts - remove plugin stanza when sunsetting
export default {
plugins: [
// ["legacy-library-expo-plugin"], ← delete
"expo-router",
],
};Related: Dead Code & Dependency Analysis
Standardize review questions so every new library gets the same scrutiny.
## Dependency PR: @shopify/flash-list
- [ ] Problem class documented (list performance - feed screen)
- [ ] Installed with `npx expo install`
- [ ] `npx expo-doctor` clean
- [ ] Native rebuild scheduled / completed
- [ ] No duplicate library (FlatList remains for settings)
- [ ] Bundle impact noted (source size + native)
- [ ] Test plan on physical Android device
- [ ] ADR updated if this changes team default# .github/workflows/pr-checks.yml - add on dependency-label PRs
- name: Expo doctor
if: contains(github.event.pull_request.labels.*.name, 'dependencies')
run: npx expo-doctordependencies - triggers extra CI stepsexpo-doctor fails - version skew is cheaper to fix in PR than in EAS queueSDK bumps are when unpinned or duplicate libraries hurt most. Run a reconciliation pass before merging the SDK PR.
# Upgrade workflow (simplified)
npx expo install expo@~57.0.4 --fix
npx expo-doctor
npx knip
npm run typecheck && npm test
# Reinstall essential native stack against new matrix
npx expo install react-native-reanimated react-native-gesture-handler \
react-native-mmkv @shopify/flash-list @sentry/react-native
eas build --profile preview --platform all| Step | Goal |
|---|---|
expo install --fix | Align all expo-* modules to SDK 57 |
expo-doctor | Catch incompatible peer deps |
knip | Remove libraries that died during the last release cycle |
| Preview EAS build | Validate native compile before production tag |
| Re-read Babel config | Reanimated plugin must stay last |
Related: Upgrading Expo SDK Versions
| Anti-pattern | Why it fails |
|---|---|
npm install every native module | Version skew → EAS native compile errors |
| Redux + Zustand + Context for same cart | Three sources of truth; logout cleanup nightmare |
| FlashList everywhere on day one | Estimate tuning cost with no measurable gain |
| Keeping unused native modules "just in case" | Autolinking bloat; SDK upgrade pain |
| Skipping dev client rebuild after native add | "Works in Metro" but crashes on device |
| Two crash reporters | Duplicate events; double SDK overhead |
TanStack Query, Zustand, MMKV, FlashList, Reanimated + Gesture Handler, and Sentry - the default production stack for Expo SDK 57 apps in this docs site.
Yes - it is JS-only with no Expo native module. Still pin a semver range and commit the lockfile. For any package Expo documents in the SDK reference, prefer npx expo install.
One Zustand store per domain (cart, preferences, onboarding) - not one mega-store. See zustand.
Whenever the package adds native code or a config plugin - MMKV, FlashList, Sentry, Reanimated. JS-only packages need Metro restart only.
Write a short ADR: problem, alternatives considered, native cost, sunset plan. Link it from the dependency PR.
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026