React Native (Expo) Installation and Setup
Jazz supports Expo through the dedicated jazz-tools/expo entry, which is specifically designed for Expo applications. If you're building for React Native without Expo, please refer to the React Native guide instead.
Jazz requires an Expo development build using Expo Prebuild for native code. It is not compatible with Expo Go. Jazz also supports the New Architecture.
Tested with:
"expo": "~53.0.0", "react-native": "0.79.2", "react": "18.3.1"
Installation
Create a new project
(Skip this step if you already have one)
npx create-expo-app -e with-tailwindcss my-jazz-app cd my-jazz-app npx expo prebuild
Install dependencies
# Expo dependencies npx expo install expo-linking expo-secure-store expo-sqlite expo-file-system @react-native-community/netinfo expo-image-manipulator # React Native polyfills npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values @bacons/text-decoder # Jazz dependencies npm i -S jazz-tools
- Requires at least Node.js v20.
- Hermes has added support for
atobandbtoain React Native 0.74. If you are using earlier versions, you may also need to polyfillatobandbtoain yourpackage.json. Packages to try includetext-encodingandbase-64, and you can drop@bacons/text-decoder. See our Troubleshooting Guide for quick fixes.
Fix incompatible dependencies
If you encounter incompatible dependencies, you can try to fix them with the following command:
npx expo install --fix
Configure Metro
Regular repositories
If you are not working within a monorepo, create a new file metro.config.js in the root of your project with the following content:
// metro.config.js const {const getDefaultConfig: anygetDefaultConfig } =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("expo/metro-config"); const {const withNativeWind: anywithNativeWind } =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("nativewind/metro"); constconst config: anyconfig =const getDefaultConfig: anygetDefaultConfig(var __dirname: stringThe directory name of the current module. This is the same as the `path.dirname()` of the `__filename`.__dirname);const config: anyconfig.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx", "cjs"];const config: anyconfig.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];var module: NodeJS.ModuleA reference to the current module.module.NodeJS.Module.exports: anyThe `module.exports` object is created by the `Module` system. Sometimes this is not acceptable; many want their module to be an instance of some class. To do this, assign the desired export object to `module.exports`.exports =const withNativeWind: anywithNativeWind(const config: anyconfig, {input: stringinput: "./src/global.css" });
Monorepos
For monorepos, use the following metro.config.js:
// metro.config.js const {const getDefaultConfig: anygetDefaultConfig } =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("expo/metro-config"); const {const FileStore: anyFileStore } =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("metro-cache"); const {const withNativeWind: anywithNativeWind } =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("nativewind/metro"); constconst path: anypath =var require: NodeJS.Require (id: string) => anyUsed to import modules, `JSON`, and local files.require("path"); // eslint-disable-next-line no-undef constconst projectRoot: stringprojectRoot =var __dirname: stringThe directory name of the current module. This is the same as the `path.dirname()` of the `__filename`.__dirname; constconst workspaceRoot: anyworkspaceRoot =const path: anypath.resolve(const projectRoot: stringprojectRoot, "../.."); constconst config: anyconfig =const getDefaultConfig: anygetDefaultConfig(const projectRoot: stringprojectRoot);const config: anyconfig.watchFolders = [const workspaceRoot: anyworkspaceRoot];const config: anyconfig.resolver.nodeModulesPaths = [const path: anypath.resolve(const projectRoot: stringprojectRoot, "node_modules"),const path: anypath.resolve(const workspaceRoot: anyworkspaceRoot, "node_modules"), ];const config: anyconfig.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx", "cjs"];const config: anyconfig.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];const config: anyconfig.cacheStores = [ newconst FileStore: anyFileStore({root: anyroot:const path: anypath.join(const projectRoot: stringprojectRoot, "node_modules", ".cache", "metro"), }), ];var module: NodeJS.ModuleA reference to the current module.module.NodeJS.Module.exports: anyThe `module.exports` object is created by the `Module` system. Sometimes this is not acceptable; many want their module to be an instance of some class. To do this, assign the desired export object to `module.exports`.exports =const withNativeWind: anywithNativeWind(const config: anyconfig, {input: stringinput: "./src/global.css" });
Additional monorepo configuration (for pnpm)
If you're using pnpm, you'll need to make sure that your expo app's package.json has this:
// package.json { "main": "index.js", ... }
For more information, refer to this Expo monorepo example.
Add polyfills
Create a file polyfills.js at the project root with the following content:
// polyfills.js import {import polyfillGlobalpolyfillGlobal } from 'react-native/Libraries/Utilities/PolyfillFunctions'; import {import ReadableStreamReadableStream } from "readable-stream";import polyfillGlobalpolyfillGlobal("ReadableStream", () =>import ReadableStreamReadableStream); // polyfill ReadableStream import "@azure/core-asynciterator-polyfill"; // polyfill Async Iterator import "@bacons/text-decoder/install"; // polyfill Text Decoder import 'react-native-get-random-values'; // polyfill getRandomValues
Update _layout.tsx:
// _layout.tsx import "../global.css"; import "../../polyfills"; import { Slot } from "expo-router"; export default function Layout() { return <Slot />; }
Lastly, ensure that the "main" field in your package.json points to index.js:
// package.json { "main": "index.js", ... }
Authentication
Jazz provides authentication to help users access their data across multiple devices. For details on implementing authentication with Expo, check our Authentication Overview guide and see the Expo Clerk Demo for a complete example.
Next Steps
Now that you've set up your Expo project for Jazz, you'll need to:
- Set up the Jazz Provider - Configure how your app connects to Jazz
- Add authentication (optional) - Enable users to access data across devices
- Define your schema - See the schema docs for more information
- Run your app:
npx expo run:ios # or npx expo run:android
Verification
Ready to see if everything's working? Let's fire up your app:
npx expo run:ios # or npx expo run:android
If all goes well, your app should start up without any angry red error screens. Take a quick look at the Metro console too - no Jazz-related errors there means you're all set! If you see your app's UI come up smoothly, you've nailed the installation.
If you run into any issues that aren't covered in the Common Issues section, drop by our Discord for help.
Common Issues
- Metro bundler errors: If you see errors about missing polyfills, ensure all polyfills are properly imported.
- iOS build failures: Make sure you've run
pod installafter adding the dependencies. - Android build failures: Ensure you've run
npx expo prebuildto generate native code. - Expo Go incompatibility: Remember that Jazz requires a development build and won't work with Expo Go.
Install CocoaPods
If you're compiling for iOS, you'll need to install CocoaPods for your project. If you need to install it, we recommend using pod-install:
npx pod-install