React Native
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": "~51.0.0", "react-native": "~0.74.5", "react": "^18.2.0",
Setup
Create a new project
(skip this step if you already have one)
npx create-expo-app -e with-router-tailwind my-jazz-app cd my-jazz-app npx expo prebuild
Install dependencies
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values @craftzdog/react-native-buffer @op-engineering/op-sqlite npm i -S jazz-tools jazz-react-native jazz-react-native-media-images
note: Hermes has added support for
atob
andbtoa
in React Native 0.74. If you are using earlier versions, you may also need to polyfillatob
andbtoa
in yourpackage.json
. Packages to try includetext-encoding
andbase-64
, and you can drop@bacons/text-decoder
.
Fix incompatible dependencies
npx expo install --fix
Install Pods
npx pod-install
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:
const { getDefaultConfig } = require("expo/metro-config"); const config = getDefaultConfig(projectRoot); config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"]; config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/]; module.exports = config;
Monorepos
For monorepos, use the following metro.config.js:
const { getDefaultConfig } = require("expo/metro-config"); const { FileStore } = require("metro-cache"); const path = require("path"); // eslint-disable-next-line no-undef const projectRoot = __dirname; const workspaceRoot = path.resolve(projectRoot, "../.."); const config = getDefaultConfig(projectRoot); config.watchFolders = [workspaceRoot]; config.resolver.nodeModulesPaths = [ path.resolve(projectRoot, "node_modules"), path.resolve(workspaceRoot, "node_modules"), ]; config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"]; config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/]; config.cacheStores = [ new FileStore({ root: path.join(projectRoot, "node_modules", ".cache", "metro"), }), ]; module.exports = config;
Additional monorepo configuration (for pnpm users)
- Add node-linker=hoisted to the root .npmrc (create this file if it doesn’t exist).
- Add the following to the root package.json:
"pnpm": { "peerDependencyRules": { "ignoreMissing": [ "@babel/*", "expo-modules-*", "typescript" ] } }
For more information, refer to this Expo monorepo example.
Add polyfills
Create a file polyfills.js
at the project root with the following content:
import { polyfillGlobal } from 'react-native/Libraries/Utilities/PolyfillFunctions'; import { Buffer } from '@craftzdog/react-native-buffer'; polyfillGlobal('Buffer', () => Buffer); import { ReadableStream } from 'readable-stream'; polyfillGlobal('ReadableStream', () => ReadableStream); import '@azure/core-asynciterator-polyfill'; import '@bacons/text-decoder/install'; import 'react-native-get-random-values';
Update index.js
based on whether you are using expo-router or not:
If using expo-router
import "./polyfills"; import "expo-router/entry";
Without expo-router
import "./polyfills"; import { registerRootComponent } from "expo"; import App from "./src/App"; registerRootComponent(App);
Lastly, ensure that the "main"
field in your package.json
points to index.js
:
"main": "index.js",
Setting up the provider
Wrap your app components with the `JazzProvider:
import { JazzProvider } from "jazz-react-native"; import { MyAppAccount } from "./schema"; export function MyJazzProvider({ children }: { children: React.ReactNode }) { return ( <JazzProvider sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }} AccountSchema={MyAppAccount} > {children} </JazzProvider> ); } // Register the Account schema so `useAccount` returns our custom `MyAppAccount` declare module "jazz-react-native" { interface Register { Account: MyAppAccount; } }
You can optionally pass a few custom attributes to <JazzProvider>
:
kvStore
ExpoSecureStoreAdapter
(default)- example:
MMKVStore
- roll your own, using MMKV
AccountSchema
Account
(default)
CryptoProvider
PureJSCrypto
(default)RNQuickCrypto
- C++ accelerated crypto provider
Choosing an auth method
Refer to the Jazz + React Native demo projects for implementing authentication:
In the demos, you'll find details on:
- Using JazzProvider with your chosen authentication method
- Defining a Jazz schema
- Creating and subscribing to covalues
- Handling invites
Working with Images
Jazz provides a complete solution for handling images in React Native, including uploading, processing, and displaying them. Here's how to work with images:
Uploading Images
To upload images, use the createImage
function from jazz-react-native-media-images
. This function handles image processing and creates an ImageDefinition
that can be stored in your Jazz covalues:
import { createImage } from "jazz-react-native-media-images"; import * as ImagePicker from 'expo-image-picker'; // Example: Image upload from device library const handleImageUpload = async () => { try { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, base64: true, // Important: We need base64 data quality: 0.7, }); if (!result.canceled && result.assets[0].base64) { const base64Uri = `data:image/jpeg;base64,${result.assets[0].base64}`; const image = await createImage(base64Uri, { owner: someCovalue._owner, // Set appropriate owner maxSize: 2048, // Optional: limit maximum image size }); // Store the image in your covalue someCovalue.image = image; } } catch (error) { console.error('Failed to upload image:', error); } };
Displaying Images
To display images, use the ProgressiveImg
component from jazz-react-native
. This component handles both images uploaded from React Native and desktop browsers:
import { ProgressiveImg } from "jazz-react-native"; import { Image } from "react-native"; // Inside your render function: <ProgressiveImg image={someCovalue.image} maxWidth={1024}> {({ src, res, originalSize }) => ( <Image source={{ uri: src }} style={{ width: 300, // Adjust size as needed height: 300, borderRadius: 8, }} resizeMode="cover" /> )} </ProgressiveImg>
The ProgressiveImg
component:
- Automatically handles different image formats
- Provides progressive loading with placeholder images
- Supports different resolutions based on the
maxWidth
prop - Works seamlessly with React Native's
Image
component
For a complete implementation example, see the Chat Example.
Running your app
npx expo run:ios npx expo run:android