Documentation Index
Fetch the complete documentation index at: https://mintlify.com/facebook/react-native/llms.txt
Use this file to discover all available pages before exploring further.
Native UI components allow you to integrate native views into your React Native application, providing access to platform-specific UI elements and optimized rendering.
What are Native UI Components?
Native UI components are platform-specific views (Android Views or iOS UIViews) that are wrapped and exposed to React Native. They allow you to:
- Use platform-specific UI widgets
- Optimize performance for complex views
- Integrate existing native UI libraries
- Access native UI features not available in React Native
Architecture
Native UI components consist of:
- View Manager: Creates and manages native view instances
- View Implementation: The actual native view code
- Props: Properties that can be set from JavaScript
- Events: Callbacks that send data back to JavaScript
Android Native UI Components
Creating a View Manager
package com.yourapp.views
import android.graphics.Color
import android.view.View
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
import android.widget.ProgressBar
class CustomProgressBarManager : SimpleViewManager<ProgressBar>() {
override fun getName(): String {
return "CustomProgressBar"
}
override fun createViewInstance(reactContext: ThemedReactContext): ProgressBar {
return ProgressBar(reactContext, null, android.R.attr.progressBarStyleHorizontal)
}
@ReactProp(name = "progress")
fun setProgress(view: ProgressBar, progress: Float) {
view.progress = (progress * 100).toInt()
}
@ReactProp(name = "color", customType = "Color")
fun setColor(view: ProgressBar, color: Int?) {
color?.let {
view.progressDrawable.setTint(it)
}
}
}
package com.yourapp.views;
import android.widget.ProgressBar;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
public class CustomProgressBarManager extends SimpleViewManager<ProgressBar> {
@Override
public String getName() {
return "CustomProgressBar";
}
@Override
protected ProgressBar createViewInstance(ThemedReactContext reactContext) {
return new ProgressBar(
reactContext,
null,
android.R.attr.progressBarStyleHorizontal
);
}
@ReactProp(name = "progress")
public void setProgress(ProgressBar view, float progress) {
view.setProgress((int) (progress * 100));
}
@ReactProp(name = "color", customType = "Color")
public void setColor(ProgressBar view, Integer color) {
if (color != null) {
view.getProgressDrawable().setTint(color);
}
}
}
Registering the View Manager
Add to your ReactPackage:
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> {
return listOf(CustomProgressBarManager())
}
Using in JavaScript
import { requireNativeComponent, StyleSheet, View } from 'react-native';
const CustomProgressBar = requireNativeComponent('CustomProgressBar');
export default function App() {
return (
<View style={styles.container}>
<CustomProgressBar
style={styles.progressBar}
progress={0.7}
color="#4CAF50"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
progressBar: {
width: 300,
height: 20,
},
});
iOS Native UI Components
Creating a View Manager
// CustomProgressBarManager.h
#import <React/RCTViewManager.h>
@interface CustomProgressBarManager : RCTViewManager
@end
// CustomProgressBarManager.m
#import "CustomProgressBarManager.h"
#import "CustomProgressBar.h"
@implementation CustomProgressBarManager
RCT_EXPORT_MODULE(CustomProgressBar)
- (UIView *)view
{
return [[CustomProgressBar alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(progress, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
@end
// CustomProgressBar.h
#import <UIKit/UIKit.h>
@interface CustomProgressBar : UIProgressView
@end
// CustomProgressBar.m
#import "CustomProgressBar.h"
@implementation CustomProgressBar
- (instancetype)init
{
if (self = [super initWithProgressViewStyle:UIProgressViewStyleDefault]) {
self.progress = 0.0;
}
return self;
}
@end
// CustomProgressBar.swift
import UIKit
class CustomProgressBar: UIProgressView {
@objc var progress: CGFloat = 0.0 {
didSet {
self.setProgress(Float(progress), animated: true)
}
}
@objc var color: UIColor = .blue {
didSet {
self.progressTintColor = color
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.progressViewStyle = .default
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// CustomProgressBarManager.m
#import <React/RCTViewManager.h>
@interface RCT_EXTERN_MODULE(CustomProgressBarManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(progress, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
@end
// CustomProgressBarManager.swift
import Foundation
@objc(CustomProgressBarManager)
class CustomProgressBarManager: RCTViewManager {
override func view() -> UIView! {
return CustomProgressBar()
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
View Properties
Android Property Types
Use @ReactProp annotation to expose properties:
// Basic types
@ReactProp(name = "text")
fun setText(view: TextView, text: String?) {
view.text = text
}
// Numbers
@ReactProp(name = "fontSize")
fun setFontSize(view: TextView, fontSize: Float) {
view.textSize = fontSize
}
// Booleans
@ReactProp(name = "enabled")
fun setEnabled(view: View, enabled: Boolean) {
view.isEnabled = enabled
}
// Colors
@ReactProp(name = "color", customType = "Color")
fun setColor(view: TextView, color: Int?) {
color?.let { view.setTextColor(it) }
}
// Arrays (using ReadableArray)
@ReactProp(name = "items")
fun setItems(view: CustomView, items: ReadableArray?) {
items?.let {
val list = mutableListOf<String>()
for (i in 0 until it.size()) {
list.add(it.getString(i))
}
view.setItems(list)
}
}
// Maps (using ReadableMap)
@ReactProp(name = "config")
fun setConfig(view: CustomView, config: ReadableMap?) {
config?.let {
if (it.hasKey("title")) {
view.setTitle(it.getString("title"))
}
}
}
iOS Property Macros
// Basic property export
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
RCT_EXPORT_VIEW_PROPERTY(fontSize, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
// Custom property with transformation
RCT_CUSTOM_VIEW_PROPERTY(color, UIColor, CustomView)
{
if (json) {
view.textColor = [RCTConvert UIColor:json];
} else {
view.textColor = defaultView.textColor;
}
}
// Remapping property names
RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment)
Handling Events
Android Events
class CustomSliderManager : SimpleViewManager<CustomSlider>() {
override fun getName() = "CustomSlider"
override fun createViewInstance(reactContext: ThemedReactContext): CustomSlider {
return CustomSlider(reactContext)
}
override fun addEventEmitters(
reactContext: ThemedReactContext,
view: CustomSlider
) {
view.setOnValueChangeListener { value ->
val event = Arguments.createMap().apply {
putDouble("value", value.toDouble())
}
reactContext
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(view.id, "onValueChange", event)
}
}
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
"onValueChange" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onValueChange",
"captured" to "onValueChangeCapture"
)
)
)
}
}
JavaScript usage:
<CustomSlider
onValueChange={(event) => {
console.log('Value:', event.nativeEvent.value);
}}
/>
iOS Events
@interface CustomSlider : UISlider
@property (nonatomic, copy) RCTBubblingEventBlock onValueChange;
@end
@implementation CustomSlider
- (instancetype)init
{
if (self = [super init]) {
[self addTarget:self
action:@selector(valueChanged:)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
- (void)valueChanged:(CustomSlider *)sender
{
if (self.onValueChange) {
self.onValueChange(@{
@"value": @(sender.value)
});
}
}
@end
@implementation CustomSliderManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[CustomSlider alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(onValueChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(value, CGFloat)
@end
Example: Activity Indicator View Manager
Here’s a real-world example from React Native’s source code (packages/react-native/React/Views/RCTActivityIndicatorViewManager.m:28-63):
@implementation RCTActivityIndicatorViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [RCTActivityIndicatorView new];
}
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
RCT_EXPORT_VIEW_PROPERTY(hidesWhenStopped, BOOL)
RCT_CUSTOM_VIEW_PROPERTY(size, UIActivityIndicatorViewStyle, UIActivityIndicatorView)
{
/*
Setting activityIndicatorViewStyle overrides the color,
so restore the original color after setting the indicator style.
*/
UIColor *oldColor = view.color;
view.activityIndicatorViewStyle = json
? [RCTConvert UIActivityIndicatorViewStyle:json]
: defaultView.activityIndicatorViewStyle;
view.color = oldColor;
}
RCT_CUSTOM_VIEW_PROPERTY(animating, BOOL, UIActivityIndicatorView)
{
BOOL animating = json ? [RCTConvert BOOL:json] : [defaultView isAnimating];
if (animating != [view isAnimating]) {
if (animating) {
[view startAnimating];
} else {
[view stopAnimating];
}
}
}
@end
Advanced Topics
View Lifecycle
Android
override fun onDropViewInstance(view: CustomView) {
super.onDropViewInstance(view)
// Clean up resources
view.cleanup()
}
override fun onAfterUpdateTransaction(view: CustomView) {
super.onAfterUpdateTransaction(view)
// Called after props are updated
view.applyChanges()
}
iOS
- (void)dropView:(UIView *)view
{
// Clean up resources
[view removeFromSuperview];
}
Commands
Expose imperative methods that can be called from JavaScript:
Android
override fun getCommandsMap(): Map<String, Int> {
return mapOf(
"focus" to COMMAND_FOCUS,
"blur" to COMMAND_BLUR
)
}
override fun receiveCommand(
view: CustomView,
commandId: String,
args: ReadableArray?
) {
when (commandId) {
"focus" -> view.requestFocus()
"blur" -> view.clearFocus()
}
}
companion object {
private const val COMMAND_FOCUS = "focus"
private const val COMMAND_BLUR = "blur"
}
iOS
RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager,
NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = viewRegistry[reactTag];
[view becomeFirstResponder];
}];
}
JavaScript usage:
import { UIManager, findNodeHandle } from 'react-native';
const ref = useRef();
const handleFocus = () => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(ref.current),
'focus',
[]
);
};
<CustomView ref={ref} />
TypeScript Support
Create type-safe interfaces for your native components:
import { ViewProps } from 'react-native';
import { HostComponent, requireNativeComponent } from 'react-native';
interface CustomProgressBarProps extends ViewProps {
progress: number;
color?: string;
onValueChange?: (event: { nativeEvent: { value: number } }) => void;
}
const CustomProgressBarNative =
requireNativeComponent<CustomProgressBarProps>('CustomProgressBar');
export default CustomProgressBarNative;
Best Practices
- Keep view managers focused on view creation and property mapping
- Use events for data flowing from native to JavaScript
- Use props for data flowing from JavaScript to native
- Clean up resources in lifecycle methods
- Implement proper error handling
- Document your component’s API and props
- Consider performance implications of frequent prop updates
- Test on both platforms when building cross-platform components
- Use TypeScript for better type safety
- Follow platform-specific design guidelines