React API
Import app-facing APIs from @symbiote-native/react:
import { Pressable, ScrollView, StyleSheet, Text, TextInput, View } from '@symbiote-native/react';The React adapter intentionally feels close to React Native. The difference is
below the component surface: host mutations go through @symbiote-native/engine.
Installation
Section titled “Installation”pnpm add @symbiote-native/react react-native reactreact-native and react stay your app’s own top-level dependencies — the
adapter replaces only the JS renderer that drives them, and react-native is
the Metro version anchor, so it has to be pinned at the app root.
@symbiote-native/engine is a peer dependency and installs alongside.
Component shape
Section titled “Component shape”| Concern | React API |
|---|---|
| Event callbacks | onPress, onValueChange (shared by Switch/TextInput), onLayout |
| Children | children?: ReactNode |
| Render children | (state) => ReactNode, for components such as Pressable |
| Refs | React refs to host instances or component handles |
| Styles | style={styles.root} with React Native-style objects, or className for a registered CSS class (see the Styling guide) |
Common events
Section titled “Common events”<Pressable onPress={event => {}} onLongPress={event => {}} /><Switch value={enabled} onValueChange={(value, event) => setEnabled(value)} /><TextInput value={text} onValueChange={(text, event) => setText(text)} /><View onLayout={event => {}} />Refs and handles
Section titled “Refs and handles”Host primitives expose the shared host instance where available. Composed components expose component-specific handles:
import { useRef } from 'react';import { TextInput, type ITextInputHandle } from '@symbiote-native/react';
const inputRef = useRef<ITextInputHandle>(null);
<TextInput ref={inputRef} value="" onValueChange={() => {}} />;inputRef.current?.focus();Runtime modules
Section titled “Runtime modules”The adapter re-exports stable runtime utilities and modules so app code can keep one import root:
import { Alert, Dimensions, Platform, StyleSheet } from '@symbiote-native/react';Some modules are pure engine utilities (Platform, StyleSheet, PixelRatio,
PlatformColor, DynamicColorIOS). Others are native-bridge consumers
(Alert, Share, Linking, Keyboard, Vibration, ActionSheetIOS,
BackHandler, ToastAndroid, PermissionsAndroid, AccessibilityInfo,
I18nManager, Settings, LayoutAnimation, InteractionManager,
StatusBar) that are shared or thinly adapted depending on lifecycle needs.
useWindowDimensions/useColorScheme hooks and findNodeHandle are also
re-exported.
On Android, Keyboard and Settings do nothing until you also install
@symbiote-native/android:
keyboardDidShow/keyboardDidHide never fire, because Android’s only stock
keyboard-event source is ReactRootView’s layout listener and the bridgeless
surface SymbioteNative mounts never triggers it, and Settings reads back null,
because RN’s Settings has no stock Android implementation at all. That package
supplies both native modules (KeyboardObserver, SettingsManager), is picked
up by Gradle autolinking, and exports nothing from JS — installing it is the
whole integration.
Animations and gestures
Section titled “Animations and gestures”Animated (both the JS and native driver) and PanResponder are re-exported
from @symbiote-native/react — see the Animations guide
for the full surface and per-driver tradeoffs.
Portals
Section titled “Portals”import { createPortal } from '@symbiote-native/react';
createPortal(children, containerNodeOrRef, key?);createPortal renders children into a different node than the calling
component’s own place in the tree — the same primitive React DOM’s
createPortal provides, backed by react-reconciler’s Fiber-level portal
instead of a DOM node. Stock React Native cannot support this: its Fabric host
config runs in persistent mode and never implements the mutation-mode
container operations a portal needs. @symbiote-native/react is mutation-mode, so
this works structurally where it never could in real RN.
The target (containerNodeOrRef) must be an already-mounted SymbioteNative host
node or SymbioteSurface — typically a ref to a persistent “overlay host”
View near the app root — not a CSS-selector-style string. v1 scope: the
target must live in the same surface as the portal’s call site; portaling
into a second, independently-mounted surface isn’t wired yet.
Vue has the equivalent through its own Teleport — see the
Vue API reference.
Cross-surface content (createTunnel)
Section titled “Cross-surface content (createTunnel)”import { createTunnel } from '@symbiote-native/react';
const overlayTunnel = createTunnel(); // module-level singleton, importable from both surfaces
function OverlayHost() { return ( <View style={styles.overlayHost}> <overlayTunnel.Out /> </View> );}
function Toast() { return toastVisible ? ( <overlayTunnel.In> <ToastCard /> </overlayTunnel.In> ) : null;}createPortal only reaches a target in the same surface. createTunnel is
for two independently mount()-ed surfaces that share no Fabric tree at all —
a split-screen embed, a system-level overlay surface. In/Out share
nothing but a small store: In registers its children wherever it renders,
any surface; Out reads that store and paints in whichever surface actually
mounts it. Neither touches a Fabric node directly, so there’s no ref-timing
gotcha and no isSymbioteNode guard to satisfy.
Vue and Angular have the same primitive — see their own API references.
App entry point (AppRegistry)
Section titled “App entry point (AppRegistry)”@symbiote-native/react exports the same AppRegistry/setHostRegistrar entry point
described in the Core API. A
component passed to setWrapperComponentProvider receives the app root as an
ordinary React children prop:
import { AppRegistry } from '@symbiote-native/react';import type { ReactNode } from 'react';
function ThemeWrapper({ children }: { children?: ReactNode }) { return <ThemeProvider>{children}</ThemeProvider>;}
AppRegistry.setWrapperComponentProvider(() => ThemeWrapper);AppRegistry.registerComponent('MyApp', () => App);Boundary
Section titled “Boundary”Do not pass React component packages to non-React adapters. A third-party React Native package that ships a JavaScript React component still uses the React dispatcher internally. Non-React adapters need native-view wrappers instead.