Expo react native app
Reusable AI agent skills for application development, data, architecture, domain modelling and delivery workflows.
npx -y skills add POWR-DATA/mtx-skills --skill expo-react-native-appAssembled 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.
What its author says it does
Copied from the file, not written here
Build cross-platform React Native apps with Expo — correct setup patterns, cross-platform gotchas, and hard-won lessons from native and web targets
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
13.2 KB, as published. Nobody here has run it
Expo React Native App
Purpose
Guide the setup and development of a cross-platform React Native app using Expo, covering icon and font configuration, Metro bundler behaviour, web-platform API access, and UI component gotchas that only surface on specific targets or themes.
When to use
When starting a new Expo project or debugging a problem that appears only on a specific platform (web, dark theme, after a package install). Apply at project setup time to avoid the class of silent failures that only surface on device or in the browser target.
Inputs expected
- Expo SDK version and target platforms (native, web, or both)
- Icon set or UI library in use (e.g.
@expo/vector-icons, custom assets) - External API endpoints the app will call
- Known errors or unexpected rendering behaviour
Guiding principles
- Vector icon fonts must be explicitly preloaded.
@expo/vector-icons(Ionicons and others) renders blank boxes unlessuseFonts(Iconset.font)is called in the root layout and rendering is gated behindfontsLoaded. This is required even when the package is already installed — installation does not trigger font registration. - Clear Metro cache after every package install. After
npx expo install <package>, runnpm start -- --clearornpx expo start --clear. A plain restart leaves stale module resolution and causes icons or modules to fail silently until the cache is cleared. - Proxy all external API calls — never branch by platform for CORS. External REST API calls that work on native are silently blocked by CORS on Expo web. The fix is a proxy (e.g. a Supabase Edge Function) so both native and web use the same code path. A web-only fetch branch doubles the maintenance surface and will diverge.
- Emoji ignores colour styling in TextInput rows. Emoji characters inside
TextInputrows are invisible on dark backgrounds because emoji rendering ignores thecolorstyle prop. Use a short text label (SHOW/HIDE) with an explicitcolorinstead. Merge the label button into a shared bordered container usingflexDirection: 'row'to avoid an unwanted internal dividing line. - Test on both native and web targets before considering a screen complete. Behaviour differences between native and web (CORS, emoji rendering, font loading) only surface when the target is exercised — do not rely on native-only testing.
- Expo Go is no longer available for SDK 56+ — use EAS development builds. Expo Go was removed from the App Store for SDK 56 (confirmed May 2026). The correct local testing approach for iOS is an EAS development build with
expo-dev-client, including when developing on Windows against a physical device. - The iOS
iconfield inapp.config.jsmust point to a real PNG. The Expo default template may leave a placeholder (e.g../assets/expo.icon) that is not a valid image. Replace it with a real PNG path (e.g../assets/images/icon.png) before building, or the iOS build fails. - Use a
Modal-based dropdown when a list sits inside nested ScrollViews on Android. AScrollViewinside an absolutely-positionedViewthat is itself inside a parentScrollViewcannot receive scroll touches — the parent clips touch events regardless ofnestedScrollEnabled. The reliable fix:measureInWindowon the trigger ref to get screen coordinates, then render the list in aModalpositioned at those coordinates, placing it entirely outside the ScrollView hierarchy. - iOS
Modalcomponents ignore the app-level orientation lock. Expo'sorientation: 'portrait'config does not propagate to modals. EachModalmust explicitly declaresupportedOrientations={['portrait']}or it rotates when the device is held sideways or in Stage Manager on iPad. - On Windows, the Android emulator lives at
C:\Users\<username>\Android\sdk\emulator\emulator.exe— not%LOCALAPPDATA%\Android\Sdk\emulator. Multipleexpo startinvocations accumulate Metro instances across ports 8081, 8083, 8085…; kill all processes on ports 8081–8090 before starting a new Metro instance, then runadb reverse tcp:8081 tcp:8081so the emulator can reach Metro on the host. - Expo/React Native and Flet/Flutter are entirely separate stacks — no patterns transfer between them. Expo uses TypeScript → React Native → native platform UI (UIKit on iOS, Jetpack Compose on Android). Flet uses Python → Flutter (Dart) → canvas-based rendering. No runtime is shared; packages, debugging approaches, and build tooling are completely different.
- Choose Expo over Flet for production mobile unless Python familiarity is the only constraint. Expo's hot reload dev cycle, EAS build/signing/OTA tooling, native push notification support, and ecosystem size substantially outweigh Flet's sole advantage of Python. If the team is comfortable with TypeScript, choose Expo.
- Expo only exposes
EXPO_PUBLIC_*env vars to client code.app.config.jsextramust read the prefixed names (e.g.process.env.EXPO_PUBLIC_SUPABASE_URL), and CI workflows must inject under those names too. Reading the unprefixedSUPABASE_URLyields empty values and a runtime "supabaseUrl is required" crash. app.config.jsextraconstants are Metro-cached. After editingapp.config.js, the old values persist until you restart withnpx expo start --clear. A stale-config error that won't go away is usually this, not a code bug.- Consolidate per-app placeholders into one
template.config.json+ a generator script. The generator stampsapp.config.js/package.json/eas.json/ the CI workflow and regenerates a singlesrc/constants/AppConfig.tsthe screens import — killing hardcoded URL/name sprawl and making a new app one edit plus one command. - Push notifications require a development build, not Expo Go. Expo push was removed from Expo Go in SDK 53 (deprecated in 52) and only works in a dev build (
expo-dev-client). The iOS Simulator cannot receive push (physical device required); EAS provisions the APNs/FCM credentials. - Native in-app review is a silent no-op on sideloaded/dev builds.
expo-store-review(Play In-App Review /SKStoreReviewController) only renders on store-distributed installs (Android: any Play track, Internal testing is enough; iOS: TestFlight/App Store). On a dev APKrequestReview()does nothing — that is correct, not a bug. Verify it as a launch-time test on a real store track; never add a dev-only fallback to force it. - Lazy-load optional native modules with a synchronous
require(). Importing an optional native module (e.g.expo-store-review) at the top level crashes any dev build whose native binary predates the JS module ("Cannot find native module");await import()then trips a Metro async-require bug ("Requiring unknown module N"). Use a synchronousrequire()inside the function (with an eslint-disable forno-require-imports) to keep graceful degradation. - Reach Metro from a physical Android device over USB with
adb reverse. Runadb reverse tcp:8081 tcp:8081, start with plainnpx expo start --dev-client(default 0.0.0.0 binding), and in the dev launcher enterhttp://localhost:8081. Do not pass--localhost— it binds Metro to IPv6::1only, breaking the IPv4 adb-reverse tunnel ("unexpected end of stream" / UNAUTHORIZED). - On Windows under OneDrive, mark
node_modules"Always keep on this device". OneDrive's on-demand placeholder files make Metro bundling fail withreadlink EINVALuntil the files are materialised locally. - Scope App Links / Universal Links to the landing path only (
/<slug>,/<slug>/), never/<slug>/*. Broadening the pattern hijacks web-only auth pages (e.g./<slug>/reset-password,/<slug>/confirm-email) into the app and breaks them — those must complete in the browser. Links also stay inert untilassetlinks.json(Android, signing SHA-256) / AASA (iOS, Apple Team ID) are deployed to/.well-known/and the app is rebuilt. - When the website's URL path differs from the Expo
slug, use a separate web-path value. The slug is a technical id tied to the EAS project — don't derive web URLs or App-Link paths from it. IntroducewebSlug/extra.webPathfor the web path and keep the slug for builds/credentials;+native-intent.tsxshould stripextra.webPath, notConstants.expoConfig.slug, so the landing link resolves to the app root even when slug ≠ URL segment.
Process
- Set up font preloading in the root layout. For every icon set in use, call
useFonts(Iconset.font)at the root layout level. Gate the return of any component tree behindif (!fontsLoaded) return null. Do not render icon-dependent components beforefontsLoadedis true. - Install packages via
npx expo install. This selects the SDK-compatible version. After every install, restart with--clearto flush Metro's module cache. - Route all external API calls through a proxy. Create a Supabase Edge Function (or equivalent server-side proxy) that forwards requests to the external API. Call the proxy from both native and web — do not use
Platform.OSbranching for fetch logic. - Audit interactive UI for dark-theme compatibility. Check any component that uses emoji, icon characters, or colour-reliant elements against a dark background. Replace emoji with text labels or
@expo/vector-iconscomponents that respect thecolorprop. - Smoke test on web target. Run
npx expo start --weband exercise all screens. CORS errors and font failures that pass on native will surface here.
Output format
- Setup checklist — font preloading wired, Metro cache strategy confirmed, proxy endpoint identified
- Screen-by-screen notes — any platform-specific issues found and how they were resolved
- Remaining work — items that need device testing beyond what the web target covers
Quality checklist
-
useFonts(Iconset.font)called for every icon set in the root layout - Rendering gated on
fontsLoadedbefore any icon-dependent component renders - Metro restarted with
--clearafter every new package install - All external API calls routed through a proxy — no
Platform.OSbranching for fetch - Interactive TextInput components use text labels, not emoji, for dark-theme compatibility
- Web target tested with
npx expo start --web - iOS
iconinapp.config.jspoints to a real PNG, not a template placeholder - iOS testing uses an EAS development build (
expo-dev-client), not Expo Go, on SDK 56+ - Portrait-locked iOS modals declare
supportedOrientations={['portrait']} - Nested-ScrollView dropdowns on Android use a
Modalrather than an inner ScrollView - Client env vars use the
EXPO_PUBLIC_*prefix inapp.config.jsextraand in CI - Push notifications, in-app review, and other store-only features tested on a real store track — not a dev build
- Optional native modules lazy-loaded via synchronous
require(), not top-level orawait import() - App Links / Universal Links scoped to the landing path only, with
assetlinks.json/AASA deployed
Avoid
- Assuming vector icons work without
useFonts— they will render as blank boxes - Restarting Metro without
--clearafter package installs — stale cache causes silent module failures - Adding a web-only fetch branch for CORS — use a proxy that works on both targets
- Using emoji in TextInput interactive elements — emoji ignores
colorand disappears on dark backgrounds - Testing only on the native simulator — web-specific failures (CORS, font loading) only surface in the browser target
- Applying Flet/Flutter patterns, packages, or mental models to Expo development — they are entirely separate stacks with no shared runtime
- Relying on Expo Go for SDK 56+ iOS testing — it is no longer available; use an EAS development build with
expo-dev-client - Leaving the Expo template's placeholder icon path in
app.config.js— point the iOSiconfield at a real PNG before building - Nesting a scrollable list inside an absolutely-positioned
Viewwithin a parentScrollViewon Android — touch events are clipped; render the list in aModalinstead - Omitting
supportedOrientations={['portrait']}on iOS modals when the app is portrait-locked — modals rotate independently of the app config - Reading unprefixed env vars (
SUPABASE_URL) in client code — onlyEXPO_PUBLIC_*names reach the client; the rest are empty at runtime - Top-level importing an optional native module — it crashes dev builds whose native binary predates it; lazy-load with
require() - Broadening App Links to
/<slug>/*— it hijacks web-only auth pages into the app; scope to the landing path only - Passing
--localhosttoexpo startfor USB device debugging — it binds IPv6-only and breaks the adb-reverse tunnel
Example usage
Starting a new Expo app — icons blank on first run, API calls working on iOS but failing on web, password toggle invisible in dark mode
Source: This skill is sourced from the Matrix Skills library. Learn more at the AI Agent Skills Library.