Introducing Brownie: Type-Safe Shared State for React Native Brownfield Apps

Authors
Oskar Kwaśniewski
Software Developer
@
Callstack

We're excited to announce the alpha release of Brownie (@callstack/brownie), a shared state management library designed specifically for React Native brownfield applications.

The Problem

Brownfield apps are existing native applications with React Native integration that face a fundamental challenge: shared state.

Your native settings screen shows the user's profile. Your React Native checkout flow needs that same user data. An authentication token refreshed in Swift needs to be available in React Native immediately. Theme preferences changed in one place should reflect everywhere.

From our experience working with clients, we've noticed a recurring pattern: every app ends up building its own proprietary Turbo Module for data sharing. Each team reinvents the wheel: writing custom native modules, maintaining type definitions on both sides, and constantly updating these modules as requirements change.

One of the examples is the Brownfield integration at Zalando. You can read about it here.

This is the main pain point of brownfield development today. These homegrown solutions are:

  • Time-consuming to build: Writing Turbo Modules requires native expertise on each platform.
  • Tedious to maintain: Every schema change means updating TypeScript, Swift, and Kotlin.
  • Hard to keep in sync: Types drift apart, leading to runtime crashes.
  • Duplicated across the industry: Every brownfield team solves the same problem from scratch.

Our Approach

Brownie takes a different path. Instead of treating React Native and native code as separate worlds, we created a single source of truth accessible from both TypeScript and Swift.

One key design decision is that state lives on the native side. This means your native app can read and write to the store independently of React Native. React Native can start in its own time. When it's ready, it will pick up the current state automatically.

The architecture is straightforward:

  1. Define your state shape once in TypeScript
  2. Generate native types automatically via CLI
  3. Access the same state from both React Native and Swift
  4. Always retrieve up to date state thanks to bi directional sync

Under the hood, state lives in a C++ layer, exposed to JavaScript via JSI and to Swift via an Objective-C++ bridge.

How It Works

1. Define Your Store

Create a *.brownie.ts file with your state shape:

// AppStore.brownie.ts
import type { BrownieStore } from '@callstack/brownie';

interface AppStore extends BrownieStore {
  counter: number;
  user: {
    name: string;
    email: string;
  };
  settings: {
    theme: 'light' | 'dark';
    notificationsEnabled: boolean;
  };
}

declare module '@callstack/brownie' {
  interface BrownieStores {
    AppStore: AppStore;
  }
}

2. Build XCFrameworks

The brownfield CLI handles codegen and packaging in one step:

npx brownfield package:ios --scheme YourScheme --configuration Release

This generates Swift types from your .brownie.ts files and builds everything into XCFrameworks:

  • YourScheme.xcframework - Your React Native module
  • Brownie.xcframework - Shared state library
  • ReactBrownfield.xcframework - Brownfield integration
  • hermesvm.xcframework - JavaScript engine

Drag these into your native Xcode project and set to Embed & Sign.

The generated Swift structs match your TypeScript schema exactly:‍‍

// Generated automatically
struct AppStore: Codable, Equatable {
    var counter: Double
    var user: User
    var settings: Settings
}

struct User: Codable, Equatable {
    var name: String
    var email: String
}

Note: Generated code is already included in Brownie.xcframework

3. Use in React Native

The useStore hook provides familiar React patterns with selector support:

import { useStore } from '@callstack/brownie';

function Counter() {
  const [counter, setState] = useStore('AppStore', (s) => s.counter);

  return (
    <Button
      title={`Count: ${counter}`}
      onPress={() => setState((prev) => ({ counter: prev.counter + 1 }))}
    />
  );
}

Selectors ensure components only re-render when their selected slice changes, just like you'd expect from a modern state library.

4. Use in SwiftUI

The @UseStore property wrapper brings the same ergonomics to SwiftUI:

import Brownie

struct CounterView: View {
  @UseStore(\\AppStore.counter) var counter

  var body: some View {
    VStack {
      Text("Count: \\(Int(counter))")

      Button("Increment") {
        $counter.set { $0 + 1 }
      }
    }
  }
}

KeyPath selectors provide type-safe access and optimal re-renders. The projected value ($counter) returns a standard SwiftUI Binding, so it works seamlessly with built-in controls like Stepper, Slider, and TextField.

5. UIKit Support

For UIKit apps, use the subscription-based API:‍

‍

class CounterViewController: UIViewController {
  private var store: Store<AppStore>?
  private var cancelSubscription: (() -> Void)?

  override func viewDidLoad() {
    super.viewDidLoad()
    store = StoreManager.get(key: AppStore.storeName, as: AppStore.self)

    cancelSubscription = store?.subscribe(\\.counter) { [weak self] counter in
      self?.updateLabel(counter)
    }
  }

  deinit {
    cancelSubscription?()
  }
}

Why We Built This

At Callstack, we work with many teams integrating React Native into existing native apps. After seeing the same pattern repeat (teams building custom Turbo Modules for data sharing, then spending ongoing effort maintaining them), we decided to solve this once and for all.

Brownie replaces those proprietary data-sharing modules with a standardized solution. Instead of writing and maintaining your own native code for state synchronization, you define your schema in TypeScript and let Brownie handle the rest.

We wanted something that:

  • Eliminates custom native modules: No more writing Turbo Modules just to share data.
  • Feels native on both sides: React hooks in JS, property wrappers in Swift.
  • Is type-safe end-to-end: TypeScript schema generates Swift types automatically.
  • Stays in sync automatically: One schema, generated types, no manual updates.

Current Status: Alpha (iOS Only)

This is an early alpha release. We're shipping iOS support first to gather feedback and iterate on the API before expanding to Android.

What works today:

  • SwiftUI integration with @UseStore property wrapper
  • UIKit integration with subscribe-based API
  • React Native useStore hook with selectors
  • Multiple stores support
  • Nested objects and complex types
  • Automatic codegen for Swift types
  • XCFramework packaging via CLI

Known limitations:

  • iOS only (Android coming soon)
  • Full state sync on updates (partial sync optimization planned)
  • APIs may change based on feedback

We Want Your Feedback

This is an early alpha release and we're actively seeking feedback. If you try Brownie, let us know what works, what doesn't, and what's missing for your use case.

Open an issue on GitHub with your thoughts.

Brownie is part of the React Native Brownfield project. Check out the documentation for detailed guides on store definitions, codegen configuration, and platform-specific usage.

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 18Article

How We Made the Brownfield Gradle Plugin 22% Faster

See how task-driven Gradle optimization made the Brownfield Gradle Plugin 22% faster for clean Android release builds.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.