In a real app
Diagnosis starts with npm ls react react-native (or the pnpm/yarn equivalent) from the app: two versions of react in the tree give the familiar “Invalid hook call”, and two copies of a native module break the iOS or Android build. A resolutions field (Yarn) or overrides (npm, pnpm) forces a single version. pnpm projects in isolated mode that keep hitting native errors can set nodeLinker: hoisted in pnpm-workspace.yaml, which the Expo docs offer as the fallback.
Native scripts are the other failure source: a Podfile or build.gradle referencing ../node_modules/react-native by relative path finds nothing when the package is hoisted. The right form uses require.resolve from Node to locate react-native and @react-native-community/cli-platform-ios, which recent Expo templates already do. A metro.config.js copied from a tutorial with watchFolders and disableHierarchicalLookup should be simplified, then npx expo start --clear to drop the cache.
In CI and on EAS Build, a monorepo needs the app folder specified and the root not excluded through .easignore. My rule for shared packages: TypeScript sources consumed directly (main pointing at src/index.ts), with no intermediate build step, as long as the package is not published.
Related terms
Metro
React Native’s JavaScript bundler: it resolves, transforms, and serializes your code into a bundle, and runs the dev server.
Autolinking
The mechanism that discovers native libraries in node_modules and adds them to the Podfile and Gradle with no manual edits.
Expo
The open-source framework React Native recommends: a native module SDK, a CLI, prebuild, Expo Router, and the EAS cloud services.
EAS Build
Expo’s hosted build service: profiles in eas.json, credentials management, and macOS and Linux runners for iOS and Android.
Prebuild (Continuous Native Generation)
The Expo command that generates the android and ios folders from app.json and config plugins instead of maintaining them by hand.