Handling Navigation in React Native Brownfield Apps

Authors

Handling navigation and presenting existing native screens from React Native-controlled screens in brownfield applications has traditionally been a tricky part of integration. There are multiple ways to make it work, but the implementation is often non-trivial, especially when you are just getting started with brownfield architecture.

This is exactly why we built: @callstack/brownfield-navigation. It helps React Native and native teams wire navigation in a clean, typed, and maintainable way, while improving developer experience on both sides.

This article is divided into three parts:

  • React Native side
  • Native App side
  • Workflow

React Native Side

Start by installing the package in your React Native app:

yarn add @callstack/brownfield-navigation

Ensure your app has Babel dependencies available (@babel/core, @react-native/babel-preset), which are used during codegen. If not, please install them.

Now define your navigation contract in a new file named brownfield.navigation.ts:

export interface BrownfieldNavigationSpec {
  navigateToSettings(): void;
  navigateToReferrals(userId: string): void;
}

Next, run codegen to generate Swift and Kotlin delegates from the schema:

npx brownfield navigation:codegen

You can skip this command in favor of running npx brownfield package:ios or npx brownfield package:android. These commands internally invoke codegen automatically. If you are not familiar with these commands, here is a quick read.

Finally, invoke the generated methods from React Native to present native screens:

import { Button, View } from "react-native";
import BrownfieldNavigation from "@callstack/brownfield-navigation";

export function NativeLinks() {
  return (
    <View>
      <Button
        title="Open native settings"
        onPress={() =>
          BrownfieldNavigation.navigateToSettings()
        }
      />
      <Button
        title="Open native referrals"
        onPress={() =>
          BrownfieldNavigation.navigateToReferrals(
            "user-123",
          )
        }
      />
    </View>
  );
}

That is all you need to handle on the React Native side.

React Native Brownfield is 100% compatible with Expo. If you want to try it on an Expo App, ensure you have followed the expo-integration docs first.

Native App Side

To complete the wiring, implement the generated delegate in your iOS application. But first, ensure you have the BrownfieldNavigation.xcframework linked to the project.

Depending on how your setup looks like, this step might differ. Dragging and dropping things into Xcode sounds fun the first time, but it gets tedious pretty fast. This is one of the first things we automate in all the incremental migration projects we do at Callstack.

Then, start by implementing BrownfieldNavigationDelegate:

import BrownfieldNavigation
import SwiftUI
import UIKit

public final class RNNavigationDelegate: BrownfieldNavigationDelegate {
  public func navigateToSettings() {
    present(SettingsScreen())
  }

  public func navigateToReferrals(_ userId: String) {
    present(ReferralsScreen(userId: userId))
  }

  private func present<Content: View>(_ view: Content) {
    DispatchQueue.main.async {
      let hostingController = UIHostingController(rootView: view)
      UIApplication.shared.topMostViewController()?
        .present(hostingController, animated: true)
    }
  }
}
The func present helper is private and not part of the delegate itself. Depending on your iOS app’s navigation structure, this implementation can vary.

Then register the delegate. An ideal place is before any React Native UI is presented:

import BrownfieldNavigation

@main
struct BrownfieldAppleApp: App {
  init() {
    BrownfieldNavigationManager.shared.setDelegate(
      navigationDelegate: RNNavigationDelegate()
    )
  }
}

That completes the native app setup. You can now run your iOS app and validate the flow.

Workflow

Codegen turns brownfield.navigation.ts into a stable, typed bridge surface that both teams implement against. The React Native team ships that surface inside artifacts (.xcframework / .aar) and hands those artifacts to native teams. Native apps consume the artifact and wire delegates to existing screens without depending on React Native internals or JavaScript tooling.

This contract-plus-artifact handoff reduces coupling and makes ownership boundaries and delivery steps simpler for both teams.

To summarize, this is how the workflow looks:

  1. You create a brownfield.navigation.ts spec in your React Native app.
  2. You run npx brownfield navigation:codegen to generate native bridge/delegate files.
  3. React Native calls BrownfieldNavigation.<method>().
  4. Native host code implements BrownfieldNavigationDelegate.
  5. You register the delegate at startup.

Each time you change brownfield.navigation.ts:

  1. You run npx brownfield navigation:codegen to regenerate native bridge/delegate files.
  2. Update your React Native call site, if required.
  3. Update native host code implementation, if required.
You can skip npx brownfield navigation:codegen in favor of npx brownfield package:ios / npx brownfield package:android.
The codegen command is intended for development only. When packaging your xcframework or aar, code generation is handled automatically by the package commands.

Final Words

@callstack/brownfield-navigation is designed to improve developer experience for both native and React Native teams, while reducing long-term maintenance costs for navigation handoff scenarios. It is especially useful in advanced entry points such as deep links and push notifications, where an imperative API can be called from anywhere in React Native.

To learn more about the complete setup and integration details, check out the Brownfield Navigation docs.

Table of contents
Adding React Native to existing apps?

We help teams introduce React Native into brownfield projects effectively.

Let’s chat

//

//
Brownfield

We can help you move
it forward!

At Callstack, we work with companies big and small, pushing React Native everyday.

New Architecture Migration

Safely migrate to React Native’s New Architecture to unlock better performance, new capabilities, and future-proof releases.

Code Sharing

Implement effective code-sharing strategies across all platforms to accelerate shipping and reduce code duplication.

Migration to React Native

Plan and execute a migration from native or hybrid stacks to React Native with minimal disruption and clear technical direction.

React Native Brownfield

Integrate React Native into existing native application and start sharing code across platforms immediately.

//
Insights

Learn more about Brownfield

Here's everything we published recently on this topic.