Skip to content

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.

Terminal window
pnpm add @symbiote-native/react react-native react

react-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.

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)
<Pressable onPress={event => {}} onLongPress={event => {}} />
<Switch value={enabled} onValueChange={(value, event) => setEnabled(value)} />
<TextInput value={text} onValueChange={(text, event) => setText(text)} />
<View onLayout={event => {}} />

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();

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.

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.

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.

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.

@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);

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.