name: react-native-runtimes
description: Install, configure, and write code with the @react-native-runtimes/core and @react-native-runtimes/state packages — named secondary React Native (Hermes) runtimes for rendering components, scheduling awaitable functions, and running headless background work, plus the C++-backed shared Zustand store. Use whenever the user mentions threaded runtimes, secondary runtimes, OnRuntime, ThreadedScreen, threadedComponent, runtimeFunction, the 'background'/'main' function directives, ThreadedRuntime.prewarm/runHeadlessTask, the .threaded-runtime/entry.js Metro generated file, createSharedStore / store.path, or wants to move long lists, chat screens, or sync engines off the main JS thread. Also use when migrating from react-native-worklets-core, react-native-multithreading, or raw JSI worklets.
metadata:
type: skill
react-native-runtimes
Two packages:
@react-native-runtimes/core— named secondary React Native runtimes (extraReactHoston Android,RCTHoston iOS, each with its own Hermes runtime and JS heap) for rendering components, scheduling awaitable functions, and running headless background work.@react-native-runtimes/state— a Zustand-shaped store whose JSON state lives in a process-wide C++ singleton, so every runtime can read and commit the same data.
This file is a router. Read it first, then load only the reference that matches the task — every reference is self-contained.
When to load which reference
- Fresh install — packages, Metro config,
index.js, iOSAppDelegate, AndroidMainApplication, minimal<ThreadedScreen>→ references/quickstart.md. Load this whenever the user is setting up the library for the first time. - Rendering a screen or component on another runtime —
OnRuntime,Threaded,ThreadedScreen,threadedComponent,ThreadedReactSurface, prop rules → references/rendering-components.md. - Awaitable cross-runtime calls —
runtimeFunction,call(fn).on(runtimeName)(...),'background'/'main'function directives,usingRuntime→ references/runtime-functions.md. Load this when the user wants a return value from work running on another runtime. - Background jobs and runtime lifecycle —
registerThreadedHeadlessTask,ThreadedRuntime.runHeadlessTask,prewarm/destroy/getRuntimeNames, native dispatch from Kotlin / Swift / C++,index.<runtime>.tsstartup files → references/headless-and-lifecycle.md. - Sharing state across runtimes —
createSharedStore,store.path([...]), sync vs async API,subtrees, persistence, multi-writer rules → references/shared-state.md. - Migrating from
react-native-worklets-core,react-native-multithreading, or raw JSI worklets → references/migration.md. Load this FIRST when the user shows worklets-core or multithreading code. - Debugging a symptom (a runtime function returns stale data, a threaded surface is blank, a native module is missing on the threaded runtime, props lose fields) → references/gotchas.md.
When unsure, start with references/quickstart.md — it grounds the rest of the API in a working setup.
Non-negotiable rules
Apply these without asking. They catch the mistakes that turn into broken builds or runtime puzzles.
- Install the Nitro peer.
react-native-nitro-modulesis required by both packages. On iOS runbundle exec pod installafter install. State needs@react-native-runtimes/state; rendering and runtime functions only needcore. - Metro wrapper is
withThreadedRuntime(from@react-native-runtimes/core/metro) — notwithRuntimes, notwithReactNativeRuntimes. Passroots: [...]covering every file that containsOnRuntime,threadedComponent,runtimeFunction, or a function directive. index.jsMUST gate onglobal.__THREADED_RUNTIME_ENV__. Threaded runtimes load the same bundle as the main runtime; without the gate, both will try to register the app component and you'll get duplicate-registration errors or blank threaded surfaces. The threaded branch shouldrequire('./.threaded-runtime/entry'); the main branch callsAppRegistry.registerComponent(...).- iOS: call
ThreadedRuntime.configure(withReactNativeDelegate:launchOptions:)inAppDelegatebefore the first surface mounts. Late configuration creates threaded runtimes without your native module set. - Android: threaded runtimes don't inherit autolinked packages. Use
ThreadedRuntime.setMainReactPackagesProvider { PackageList(this).packages }to give threaded runtimes the same module set as the main runtime, orsetExtraReactPackagesProvider { listOf(...) }to curate a smaller list. Without this, native module calls from the threaded runtime return undefined. - Closures don't cross runtimes. A
runtimeFunctionbody executes inside the target runtime, which has its own module evaluation and its own copies of every module-scope variable. Pass everything the function needs as arguments, or read it through a shared store. Mutating a module-scopeleton the main runtime does not affect any other runtime. - Everything across the boundary is JSON. Function args, return values,
OnRuntimeprops, headless task payloads, shared store values — allJSON.stringify'd. No functions, refs, class instances,Map/Set,BigInt,Error, or circular refs.Datebecomes{}unless you.toISOString()first. runHeadlessTaskresolves on dispatch, not on completion. If the caller needs the task's return value, useruntimeFunctioninstead — its Promise waits for the body to finish.OnRuntime's child must be a direct, top-level component reference. Ternaries, prop-forwarded children, and wrappers (<Suspense>, etc.) break Metro's static analysis. Move the condition outside or usethreadedComponent+<Threaded>explicitly.- Shared store:
.get()/.use()/.getRevision()are SYNCHRONOUS..set()/.update()/.hydrate()/.clear()are async. Don'tawait path.get()— it doesn't return a Promise, and awaiting a plain value silently masks future bugs where the call site forgets to add an await. - Use
update(prev => ...)whenever two runtimes can write the same path.set(snapshot)of a stale read clobbers concurrent writes. - Hermes only. Verify
hermesEnabled=true(Android) and the iOS Podfile uses Hermes. JSC is not a supported target for threaded runtimes.
Operating rules
- For per-frame animation, recommend worklets, not this library. This library is for screen-scoped or app-lifetime work. Use
react-native-worklets-core/ Reanimated worklets for the animation loop. - Prewarm before navigation. A cold runtime + bundle parse is hundreds of ms. While a picker/list is on screen, kick off
ThreadedRuntime.prewarm(nextRuntimeName). Repeat ononPressIn— cheap if the runtime already exists. - Stable runtime names per logical owner.
conversation-${conversationId}-runtimeis fine — same id always picks up the same runtime. A name that changes per render is a memory leak. - Prefer the function directive for fixed-runtime helpers. Top-level
async function refreshCache() { 'background'; ... }keeps call sites looking like ordinary functions while still scheduling the work on the named runtime. UseruntimeFunction+call(fn).on(name)(...)when the caller picks the runtime. - Background-only startup goes in
index.<runtime>.ts. The Metro wrapper discovers root-level files named like that and conditionally requires them based onglobal.__THREADED_RUNTIME_ENV__. Register headless tasks, hydrate stores, and start queues there — never UI imports. destroyOnUnmountonly for genuinely single-use routes. For chat threads or any screen the user re-enters often, leave the runtime alive; otherwise every re-entry pays the bundle-load cost.- Pass ids, not data. Props serialize on every render. For a long chat list, write messages into a shared store path and pass only the
conversationId; the threaded screen subscribes to that path. - Verify peer dependency installs. A native crash after install is almost always a missing
react-native-nitro-modules(both platforms), or on Android a missingThreadedZustandPackage()insetExtraReactPackagesProvider. - Never invent APIs. If a method or option is not in the references, say so and tell the user to check the package. Hallucinations to specifically avoid:
withRuntimes,ThreadedRuntime.register(...),threadedComponent(Component, { runtime: fn }), awaiting.get().
Authoritative links
- Main repo: https://github.com/margelo/react-native-runtimes
- Core package:
packages/core/README.mdin the repo - State package:
packages/state/README.mdin the repo - Example app:
example/App.tsxandexample/src/examples/*for working code