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 and btoa in React Native 0.74. If you are using earlier versions, you may also need to polyfill atob and btoa in your package.json . Packages to try include text-encoding and base-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} targetWidth={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 targetWidth 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