Skip to main content

View Components

Nitro provides first-class support for creating React Native Views.

Such views can be rendered within React Native apps using Fabric, and are backed by a C++ ShadowNode. The key difference to a Fabric view is that it uses Nitro for prop parsing, which is more lightweight, performant and flexible.

note

Nitro Views require react-native 0.78.0 or higher, and require the new architecture.

Create a Nitro View

1. Declaration

To create a new Nitro View, declare it's props and methods in a *.nitro.ts file, and export your HybridView type:

Camera.nitro.ts
import type { HybridView } from 'react-native-nitro-modules'

export interface CameraProps {
enableFlash: boolean
}
export interface CameraMethods { }

export type CameraView = HybridView<CameraProps, CameraMethods>

2. Code Generation

Then, run nitrogen:

npx nitro-codegen

This will create a C++ ShadowNode, with an iOS (Swift) and Android (Kotlin) interface, just like any other Hybrid Object. Additionally, a view config (CameraViewConfig.json) will be generated - this is required by Fabric.

3. Implementation

Now it's time to implement the View - simply create a new Swift/Kotlin file and extend from HybridCameraViewSpec:

HybridCameraView.swift
class HybridCameraView : HybridCameraViewSpec {
// Props
var isBlue: Bool = false

// View
var view: UIView = UIView()
}

Just like any other Hybrid Object, add the Hybrid View to your nitro.json's autolinking configuration:

nitro.json
{
// ...
"autolinking": {
"CameraView": {
"swift": "HybridCameraView",
"kotlin": "HybridCameraView"
}
}
}

Now run nitrogen again.

5. Initialization

Then, to use the view in JavaScript, use getHostComponent(..):

import { getHostComponent } from 'react-native-nitro-modules'
import CameraViewConfig from '../nitrogen/generated/shared/json/CameraViewConfig.json'

export const Camera = getHostComponent<CameraProps, CameraMethods>(
'Camera',
() => CameraViewConfig
)

6. Rendering

And finally, render it:

function App() {
return <Camera enableFlash={true} />
}

Props

Since every HybridView is also a HybridObject, you can use any type that Nitro supports as a property - including custom types (interface), ArrayBuffer, and even other HybridObjects!

For example, a custom <ImageView> component can be used to render custom Image types:

Image.nitro.ts
export interface Image extends HybridObject {
readonly width: number
readonly height: number
save(): Promise<string>
}
ImageView.nitro.ts
import { type Image } from './Image.nitro.ts'
export interface ImageProps {
image: Image
}
export type ImageView = HybridView<ImageProps>

Then;

function App() {
const image = await loadImage('https://...')
return <ImageView image={image} />
}

Callbacks have to be wrapped

Whereas Nitro allows passing JS functions to native code directly, React Native core doesn't allow that. Instead, functions are wrapped in an event listener registry, and a simple boolean is passed to the native side. Unfortunately React Native's renderer does not allow changing this behaviour, so functions cannot be passed directly to Nitro Views. As a workaround, Nitro requires you to wrap each function in an object, which bypasses React Native's conversion.

So every function (() => void) has to be wrapped in an object with one key - f - which holds the function: { f: () => void }

export interface CameraProps {
onCaptured: (image: Image) => void
}
export type CameraView = HybridView<CameraProps>

function App() {
return <Camera onCaptured={(i) => console.log(i)} />
return <Camera onCaptured={{ f: (i) => console.log(i) }} />
}

Methods

Since every HybridView is also a HybridObject, methods can be directly called on the object. Assuming our <Camera> component has a takePhoto() function like so:

export interface CameraProps { ... }
export interface CameraMethods {
takePhoto(): Promise<Image>
}

export type CameraView = HybridView<CameraProps, CameraMethods>

To call the function, you would need to get a reference to the HybridObject first using hybridRef:

function App() {
return (
<Camera
hybridRef={{
f: (ref) => {
const image = ref.takePhoto()
}
}}
/>
)
}

Note: If you're wondering about the { f: ... } syntax, see "Callbacks have to be wrapped".

The ref from within hybridRef's callback is pointing to the HybridObject directly - you can also pass this around freely.