There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like
TextInput, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, we can wrap up these existing components for seamless integration with your React Native application.
Like the native module guide, this too is a more advanced guide that assumes you are somewhat familiar with iOS programming. This guide will show you how to build a native UI component, walking you through the implementation of a subset of the existing
MapView component available in the core React Native library.
Let's say we want to add an interactive Map to our app - might as well use
Native views are created and manipulated by subclasses of
RCTViewManager. These subclasses are similar in function to view controllers, but are essentially singletons - only one instance of each is created by the bridge. They expose native views to the
RCTUIManager, which delegates back to them to set and update the properties of the views as necessary. The
To expose a view you can:
RCTViewManagerto create a manager for your component.
- Add the
- Implement the
Note: Do not attempt to set the
backgroundColor properties on the
UIView instance that you expose through the
UIView instance you want to style in another
UIView and return the wrapper
UIView instead. See Issue 2948 for more context.
In the example above, we prefixed our class name with
RNT. Prefixes are used to avoid name collisions with other frameworks. Apple frameworks use two-letter prefixes, and React Native uses
RCTas a prefix. In order to avoid name collisions, we recommend using a three-letter prefix other than
RCTin your own classes.
Make sure to use
Note: When rendering, don't forget to stretch the view, otherwise you'll be staring at a blank screen.
The first thing we can do to make this component more usable is to bridge over some native properties. Let's say we want to be able to disable zooming and specify the visible region. Disabling zoom is a boolean, so we add this one line:
Note that we explicitly specify the type as
BOOL - React Native uses
RCTConvert under the hood to convert all sorts of different data types when talking over the bridge, and bad values will show convenient "RedBox" errors to let you know there is an issue ASAP. When things are straightforward like this, the whole implementation is taken care of for you by this macro.
Now to actually disable zooming, we set the property in JS:
To document the properties (and which values they accept) of our MapView component we'll add a wrapper component and document the interface with React
Now we have a nicely documented wrapper component to work with. Note that we changed
requireNativeComponent's second argument from
null to the new
Next, let's add the more complex
region prop. We start by adding the native code:
Ok, this is more complicated than the
BOOL case we had before. Now we have a
MKCoordinateRegion type that needs a conversion function, and we have custom code so that the view will animate when we set the region from JS. Within the function body that we provide,
json refers to the raw value that has been passed from JS. There is also a
view variable which gives us access to the manager's view instance, and a
defaultView that we use to reset the property back to the default value if JS sends us a null sentinel.
You could write any conversion function you want for your view - here is the implementation for
MKCoordinateRegion via a category on
RCTConvert. It uses an already existing category of ReactNative
These conversion functions are designed to safely process any JSON that the JS might throw at them by displaying "RedBox" errors and returning standard initialization values when missing keys or other developer errors are encountered.
To finish up support for the
region prop, we need to document it in
propTypes (or we'll get an error that the native prop is undocumented), then we can set it like any other prop:
Here you can see that the shape of the region is explicit in the JS documentation - ideally we could codegen some of this stuff, but that's not happening yet.
Sometimes your native component will have some reserved properties that you don't want to be part of the API for the associated React component. For example,
Switch has a custom
onChange handler for the raw native event, and exposes an
onValueChange handler property that is invoked with the boolean value rather than the raw event. Since you don't want these native only properties to be part of the API, you don't want to put them in
propTypes, but if you don't you'll get an error. The solution is to add them to the
nativeOnly option, e.g.
So now we have a native map component that we can control freely from JS, but how do we deal with events from the user, like pinch-zooms or panning to change the visible region?
Until now we've only returned a
MKMapView instance from our manager's
-(UIView *)view method. We can't add new properties to
MKMapView so we have to create a new subclass from
MKMapView which we use for our View. We can then add a
onRegionChange callback on this subclass:
Note that all
RCTBubblingEventBlock must be prefixed with
on. Next, declare an event handler property on
RNTMapManager, make it a delegate for all the views it exposes, and forward events to JS by calling the event handler block from the native view.
In the delegate method
-mapView:regionDidChangeAnimated: the event handler block is called on the corresponding view with the region data. Calling the
A React Native view can have more than one child view in the view tree eg.
In this example, the class
MyNativeView is a wrapper for a
NativeComponent and exposes methods, which will be called on the iOS platform.
MyNativeView is defined in
MyNativeView.ios.js and contains proxy methods of
When the user interacts with the component, like clicking the button, the
MyNativeView changes. In this case
UIManager would not know which
MyNativeView should be handled and which one should change
backgroundColor. Below you will find a solution to this problem:
Now the above component has a reference to a particular
MyNativeView which allows us to use a specific instance of
MyNativeView. Now the button can control which
MyNativeView should change its
backgroundColor. In this example let's assume that
MyNativeView.ios.js contains code as follow:
callNativeMethod is our custom iOS method which for example changes the
backgroundColor which is exposed through
MyNativeView. This method uses
UIManager.dispatchViewManagerCommand which needs 3 parameters:
(nonnull NSNumber \*)reactTag- id of react view.
commandID:(NSInteger)commandID- Id of the native method that should be called
commandArgs:(NSArray<id> \*)commandArgs- Args of the native method that we can pass from JS to native.
callNativeMethod is defined in the
RNCMyNativeViewManager.m file and contains only one parameter which is
(nonnull NSNumber*) reactTag. This exported function will find a particular view using
addUIBlock which contains the
viewRegistry parameter and returns the component based on
reactTag allowing it to call the method on the correct component.
Since all our native react views are subclasses of
UIView, most style attributes will work like you would expect out of the box. Some components will want a default style, however, for example
UIDatePicker which is a fixed size. This default style is important for the layout algorithm to work as expected, but we also want to be able to override the default style when using the component.
DatePickerIOS does this by wrapping the native component in an extra view, which has flexible styling, and using a fixed style (which is generated with constants passed in from native) on the inner native component:
RCTDatePickerIOSConsts constants are exported from native by grabbing the actual frame of the native component like so:
This guide covered many of the aspects of bridging over custom native components, but there is even more you might need to consider, such as custom hooks for inserting and laying out subviews. If you want to go even deeper, check out the source code of some of the implemented components.