# React API

> The React adapter public contract.

Primitives (`view`, `text`, `pressable`, `text-input`, `scroll-view`, ...) are
plain lowercase intrinsic tags — write them directly in JSX, no import needed.
Import only what's still a real component or module from `@symbiote-native/react`:

```tsx
import { StyleSheet } 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

```sh
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.

## Component shape

| Concern         | React API                                                                                                                                        |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Event callbacks | `onPress`, `onValueChange` (shared by `switch`/`text-input`), `onLayout`                                                                          |
| Children        | `children?: ReactNode`                                                                                                                           |
| Render children | list components only (`FlatList`, `SectionList`, ...) — a primitive tag like `pressable` has no component body to call a child function against; wire `onPressIn`/`onPressOut` into local state instead |
| 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](/docs/learn/styling/)) |

## Common events

```tsx
<pressable onPress={event => {}} onLongPress={event => {}} />
<switch value={enabled} onValueChange={(value, event) => setEnabled(value)} />
<text-input value={text} onValueChange={(text, event) => setText(text)} />
<view onLayout={event => {}} />
```

## Refs and handles

Host primitives expose the shared host instance where available. Composed
components expose component-specific handles:

```tsx
import { useRef } from 'react';
import type { ITextInputHandle } from '@symbiote-native/react';

const inputRef = useRef<ITextInputHandle>(null);

<text-input ref={inputRef} value="" onValueChange={() => {}} />;
inputRef.current?.focus();
```

## Runtime modules

The adapter re-exports stable runtime utilities and modules so app code can keep
one import root:

```tsx
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`](/docs/packages/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

`Animated` (both the JS and native driver) and `PanResponder` are re-exported
from `@symbiote-native/react` — see the [Animations guide](/docs/learn/animations/)
for the full surface and per-driver tradeoffs.

## Portals

```tsx
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`, Angular through
`PortalDirective`/`PortalOutletDirective`, and Svelte and Solid through their
own `Portal` — see the [Vue](/docs/api/vue/#portals-teleport),
[Angular](/docs/api/angular/#portals), [Svelte](/docs/api/svelte/#portals),
and [Solid](/docs/api/solid/#portals-portal) API references.

## Cross-surface content (`createTunnel`)

```tsx
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, Angular, Svelte, and Solid all have the same primitive — see their own
API references.

## App entry point (`AppRegistry`)

`@symbiote-native/react` exports the same `AppRegistry`/`setHostRegistrar` entry point
described in the [Core API](/docs/api/core/#app-entry-point-appregistry). A
component passed to `setWrapperComponentProvider` receives the app root as an
ordinary React `children` prop:

```tsx
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

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.
