new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Expo & React Native · all subjects

guides

835 notes in this subject, read out of this brain and free to use. This is page 3 of 14.

Start Metro web development server

To start the development server for web, run `npx expo start --web` (npm), `yarn expo start --web` (yarn), `pnpm expo start --web` (pnpm), or `bun expo start --web` (bun). Alternatively, press W in the Expo CLI terminal UI.

Override default index.html in Metro web

You can overwrite the default index.html in Metro web by creating a public/index.html file in your project.

Metro web supports static files from public directory

Expo's Metro implementation supports hosting static files from the dev server by putting them in the root public/ directory. When exporting with `npx expo export`, the contents of the public directory are copied into the dist/ directory. Your app can expect to fetch these assets relative to the host URL. The most common example is public/favicon.ico.

TypeScript path aliases in Expo Metro

Expo's Metro config supports the `compilerOptions.paths` and `compilerOptions.baseUrl` fields in the project's tsconfig.json (or jsconfig.json) file. This enables absolute imports and aliases in the project. This feature requires additional setup in existing React Native projects.

Module alias resolution changes require dev server restart

Changes to module aliases made in resolveRequest are visible the next time you restart the dev server. Resolutions are never cached and do not need the `--clear` flag to update. If you use a transform-based system like `babel-plugin-module-resolver`, you will need to clear the cache to see changes applied.

On-demand filesystem in SDK 56+

From SDK 56, Expo's file map supports on-demand filesystem access. This means `watchFolders` no longer need to include every module your app bundles, and projects that symlink to dependencies outside of the project root will now resolve correctly. The on-demand filesystem is controlled by the `experiments.onDemandFilesystem` flag in your app config and is enabled by default.

Expo CLI automatically splits bundles based on async imports

Expo CLI automatically splits bundles based on async imports for web-only. This technique can be used with Expo Router to automatically split the bundle based on route files in the app directory, loading only the code required for the current route and deferring additional JavaScript until the user navigates to different pages.

Example of module aliases in metro.config.js

To alias 'old-module' to 'new-module': const ALIASES = { 'old-module': 'new-module', }; config.resolver.resolveRequest = (context, moduleName, platform) => { return context.resolveRequest( context, ALIASES[moduleName] ?? moduleName, platform ); };

Apply aliases only to specific platforms

You can check the `platform` argument in the resolveRequest function to apply aliases only on certain platforms. For example, to apply an alias only when bundling for web: if (platform === 'web') { return context.resolveRequest(context, ALIASES[moduleName] ?? moduleName, platform); } return context.resolveRequest(context, moduleName, platform);

Checkbox and Picker controlled patterns

Checkbox from expo-checkbox pairs value with onValueChange. Picker from @react-native-picker/picker pairs selectedValue with onValueChange. Both follow the same controlled pattern as TextInput.

Mask input libraries for React Native

For masking and formatting edge cases like international phone numbers and credit card numbers, use: react-native-mask-input or react-native-mask-text for masking. For form state and validation across many fields use React Hook Form or Formik, wrapping TextInput in React Hook Form's Controller component because register does not bind to native inputs.

Control Switch component pattern

Switch is always controlled. Pair value with onValueChange: <Switch value={enabled} onValueChange={setEnabled} />. Unlike TextInput which opts into controlled mode only with value prop, Switch always requires this pattern or it reverts to the initial value.

Control RefreshControl with refreshing prop

RefreshControl treats refreshing as a controlled prop. Set it to true inside onRefresh and back to false when the refresh finishes. If it never changes, the indicator stops immediately.

Cursor position with formatting in React Native

In React Native apps using the New Architecture, typing in the middle of the field keeps the cursor in place as long as onChangeText passes the text back unchanged. When it returns transformed text, the native field must map the old cursor position to the new string, and that mapping can miss when the transform inserts or removes characters.

Keep cursor stable while formatting strategies

Three strategies to keep the cursor stable while formatting: prefer native props such as maxLength and editable over reimplementing them in JavaScript; use a mask library that handles cursor math; move formatting onto the UI thread using worklets.

Force uppercase TextInput example

To force uppercase, transform text in onChangeText and pair with autoCapitalize prop. Example: onChangeText={text => setCode(text.toUpperCase())} with autoCapitalize="characters" so the keyboard produces capital letters.

@expo/ui universal TextInput with worklets

The universal TextInput from @expo/ui (SDK 56 and later) removes formatting flicker by using worklets. Its value is an observable state object created with useNativeState, and onChangeText can be a worklet that runs synchronously on the UI thread. The formatted value lands in the same frame as the keystroke.

Worklet TextInput formatting example with selection handling

When formatting in a worklet, update both phone.value and selection.value together. Use the check formatted !== value to skip rewrite when formatting leaves text unchanged. Write selection.value with phone.value to keep cursor in sync. Example: if (formatted !== value) { phone.value = formatted; selection.value = { start: formatted.length, end: formatted.length }; }

keyboardType does not enforce input

Setting keyboardType to values like number-pad, decimal-pad, or phone-pad shows a matching keyboard but does not enforce anything. Hardware keyboards and pasted text can still insert other characters, so keep the onChangeText filter to restrict input.

TextInput onChangeText handler pattern

React Native uses onChangeText as the change handler, which receives the new text directly instead of an event object. Set a TextInput as controlled by pairing a value prop with an onChangeText callback that updates state: <TextInput value={name} onChangeText={setName} placeholder="Name" />.

Controlled vs uncontrolled TextInput comparison

Controlled inputs: value lives in React state, set with value and onChangeText, read with React state, re-render on every keystroke, use for live validation and formatting. Uncontrolled inputs: value lives in native input, set with defaultValue, read with onChangeText/onSubmitEditing/onEndEditing, no re-renders per keystroke, use for search boxes and read-on-submit forms.

Differences between React Native and web controlled inputs

Three key differences: the change handler is onChangeText receiving new text directly, not an event object; React Native has no preventDefault, so you cannot block a keystroke before it appears—sanitize text in onChangeText and pass the result back through value; the update is asynchronous, so the field reflects your state one round-trip later instead of synchronously.

Format text in onChangeText example

To format text as the user types, transform the string inside onChangeText and store the formatted result back in state. Example formatting a phone number: onChangeText={text => setPhone(formatPhone(text))} where formatPhone strips non-digits and applies formatting rules.

TextInput controlled components basics

A controlled component keeps the input value in state you manage. Passing a value prop to TextInput makes it controlled. From then on, React forces the native field to match that prop. React Native's update loop is asynchronous: a keystroke updates the native field first, then fires onChangeText. Your state update triggers a re-render, and the new value travels back to the native field.

Restrict TextInput characters example

To restrict input, let onChangeText fire, strip unwanted characters, and push the clean value back through value. Example keeping only digits: onChangeText={text => setAmount(text.replace(/[^0-9]/g, ''))}. For length limits and read-only fields, prefer maxLength and editable props instead, as they apply on the native side without flicker.

EXPO_PUBLIC_ prefix for environment variables

Environment variables must have an EXPO_PUBLIC_ prefix to be automatically loaded from .env files by Expo CLI and inlined into JavaScript code. Only variables with this prefix are included in the app bundle.

Reading environment variables in source code

Access environment variables in JavaScript using process.env.EXPO_PUBLIC_[VARNAME] notation. The variable reference is replaced with its value during bundling when you run npx expo start.

Environment variable reload behavior

Variables can be updated as you edit your code without restarting the Expo CLI or clearing the cache. You must perform a full reload (such as shake gesture and then Reload in Expo Go or your development build) to see the updated value.

Static reference requirement for environment variables

Every environment variable must be statically referenced as a property of process.env using JavaScript's dot notation (for example, process.env.EXPO_PUBLIC_KEY) for it to be inlined. Alternative versions such as process.env['EXPO_PUBLIC_KEY'] or destructuring (const {EXPO_PUBLIC_X} = process.env) are not supported and will not be inlined.

Code inside node_modules not affected by environment variable inlining

Expo CLI does not replace environment variable references in code inside node_modules for security purposes.

.env file creation and location

Create a .env file in the root of your project directory. Add environment-specific variables on new lines in the form of EXPO_PUBLIC_[NAME]=VALUE.

Migrating from babel-plugin-transform-inline-environment-variables

To migrate from babel-plugin-transform-inline-environment-variables: (1) Set variables in .env file with EXPO_PUBLIC_ prefix; (2) Update variable names in code to use EXPO_PUBLIC_ prefix; (3) Remove the plugin from Babel config; (4) Run npx expo start --clear to clear the cache.

NODE_ENV not recommended for switching .env files

Do not use NODE_ENV to switch between .env files. While technically possible (NODE_ENV=test npx expo start will load .env.test), it may not behave as expected. For example, npx expo export always forces NODE_ENV to production, so NODE_ENV=test npx expo export will not run with NODE_ENV set to test. EAS Build users should consider using eas env:pull instead to swap .env.local with an environment of choice.

Disabling environment variable features

Environment variables have two parts that can be disabled: (1) Expo CLI automatically loads .env files into the global process (disable with EXPO_NO_DOTENV=1); (2) Expo's Metro config includes inline serialization of environment variables in the client bundle (disable with EXPO_NO_CLIENT_ENV_VARS=1).

Multiple .env files and priority loading

You can define standard .env files such as .env and .env.local, and they will load according to standard priority. Commit the default .env file or other standard configurations, but generally .env.local files should be added to .gitignore because they are used to specify environment configuration specific to your local machine.

Security warning: EXPO_PUBLIC_ variables are visible in app

Never store sensitive secrets in environment variables prefixed with EXPO_PUBLIC_. These variables will be visible in plain-text in your compiled application because end-users have access to all code and embedded environment variables.

Migrating from direnv

To migrate from direnv: (1) Move environment variables used in JavaScript from .envrc file to .env file with EXPO_PUBLIC_ prefix; (2) Remove references to expo-constants and dynamic app config that read from process.env; (3) Access variables directly via process.env.EXPO_PUBLIC_[VARNAME]. Continue using direnv for environment variables not used in JavaScript code.

Migrating from react-native-config

To migrate from react-native-config: (1) Update .env files to prefix variables with EXPO_PUBLIC_ (for example, change API_URL to EXPO_PUBLIC_API_URL); (2) Update code to use process.env.EXPO_PUBLIC_[VARNAME] instead of importing Config from 'react-native-config'. Non-standard .env files must be migrated to standard .env files.

Facebook Android Package name configuration

The Package name field in Facebook's Android platform configuration comes from the android.package field in your app's config file.

Facebook Key hash configuration from Play Store

To obtain the Key hash for Facebook's Android platform configuration, go to Play Store Console and navigate to Release > Setup > App Integrity > App signing key certificate to get the SHA-1 certificate fingerprint. Convert the Hex value of the certificate to Base64 and add it under Android > Key hashes in the Facebook project.

react-native-fbsdk-next requires development build

The react-native-fbsdk-next library cannot be used in Expo Go because it requires custom native code. A development build is required to use this library with Expo.

Facebook authentication setup requires published Play Store app

To add Android as a platform in a Facebook project, your app must be approved by Google Play Store and have a valid Play Store URL. Unpublished apps without a valid Play Store URL will not be recognized by Facebook's configuration system.

Facebook Android platform configuration fields

When adding the Android platform to a Facebook project's Settings > Basic, you must provide three fields: Key hash, Package name, and Class name.

Facebook Android Class name default value

The Class name field for Facebook's Android platform configuration defaults to MainActivity. The format is package.MainActivity where package is the android.package value from your app config, for example com.myapp.example.MainActivity.

react-native-fbsdk-next library location

The react-native-fbsdk-next library is available at https://github.com/thebergamo/react-native-fbsdk-next/ and provides a wrapper around Facebook's Android and iOS SDKs for Expo projects.

react-native-purchases library for in-app purchases

The react-native-purchases library is an open-source framework that provides a wrapper around Google Play Billing and StoreKit APIs. It integrates with RevenueCat services to support in-app purchases, product management, and analytics. It works with CNG and Config Plugins and enables simplified workflows for in-app purchase requirements that may extend beyond client code, including validating purchases on an app's backend.

In-app purchases require development builds, not Expo Go

In-app purchase libraries require configuring custom native code. Custom native code cannot be configured when using Expo Go. To use a native library for in-app purchases in your project, you must create a development build instead.

expo-iap library for in-app purchases

The expo-iap library is a React Native library for in-app purchases that conforms to the OpenIAP specification and works with development builds.

Google authentication libraries for Expo

Two libraries are available for integrating Google authentication in Expo apps: react-native-nitro-google-signin and @react-native-google-signin/google-signin. Both provide native sign-in buttons and support user authentication plus authorization for Google APIs. Both require custom native code, so a config plugin and development build are necessary.

react-native-nitro-google-signin vs @react-native-google-signin/google-signin

react-native-nitro-google-signin includes built-in support for Android Credential Manager. @react-native-google-signin/google-signin provides Android Credential Manager APIs as part of their paid offering. The legacy Google Sign-In SDK for Android (com.google.android.gms:play-services-auth) is deprecated, and Google recommends migrating to Android Credential Manager.

Google Play Store upload recommended for production

For apps intending to run in production with Google Sign In, uploading the app to Google Play Store is recommended. Apps can be submitted to stores for testing even during development, allowing testing of Google Sign In with both EAS-signed builds and Google Play App Signing.

Google Sign-In requires development build

Google Sign-In libraries cannot be used in Expo Go because they require custom native code. A development build is required to use either react-native-nitro-google-signin or @react-native-google-signin/google-signin.

Firebase configuration files for Google Sign-In

When using Firebase for Android and iOS, google-services.json and GoogleService-Info.plist must be available in EAS for building. These files can be checked into the repository (they should not contain sensitive values) or treated as secrets, added to .gitignore, and made available in EAS using environment variables.

SHA-1 certificate fingerprints for Google authentication

When configuring a Google project for Android, two types of SHA-1 certificate fingerprint values can be provided. The fingerprint of the .apk file is found in Google Play Console under Release > Setup > App Integrity > Upload key certificate. The fingerprint(s) of a production app are found in Google Play Console under Release > Setup > App Integrity > App signing key certificate.

Debug build creation with Expo CLI

To quickly build and iterate on a debug build locally, use Expo CLI's npx expo run:[android|ios] commands. These commands compile your project using your locally installed Android SDK or Xcode into a debug build of your app.

Local builds complement EAS Build

Building your app locally complements EAS Build. You can keep using the build service for cloud automation and fall back to local builds for development.

Release build creation locally

To create a release build (also known as production build) of your app locally, you generate signing credentials by utilizing tools provided by Android Studio and Xcode. Then you generate a release build and follow the process of manually submitting your app to Google Play Store or Apple App Store.

Local build scenarios and use cases

You should build your app locally in these scenarios: when you want to iterate quickly on native code changes or test platform-specific changes in your debug build, when you want to manually generate native code to test your debug build, in any scenario where you are required to create builds inside an environment where access to a network is restricted, when you want to locally manage your own credentials such as upload key, when you want to test or integrate your own custom build cache provider, or when you want to opt out of prebuilt Expo Modules for Android and compile them from source locally.

Build cache providers for local development

You can accelerate your local development by caching and reusing builds from a provider. You can use EAS as a build provider or create your own custom provider.

Prebuilt Expo Modules for Android

Expo ships prebuilt Expo Modules for Android that reduce the work Gradle performs on each build. You can continue using the defaults or selectively opt out when you need to modify a module's source code.

Give your agent this brain