How to: wrap a third-party native view
You want to use a third-party RN library’s native view (e.g.
@react-native-community/slider) from a non-React adapter.
The trick: register the native ViewConfig, skip the React component
Section titled “The trick: register the native ViewConfig, skip the React component”The engine already derives a view’s events and prop processors from RN’s
ReactNativeViewConfigRegistry at runtime. A non-React adapter can drive any
native view through the same createNode-by-ViewConfig path SymbioteNative uses
for its own primitives — you register the ViewConfig from the library’s
codegen spec module, not its default export:
// register.ts — a side-effect import, no hooks in this moduleimport '@react-native-community/slider/dist/RNCSliderNativeComponent';Then each adapter gets its own thin entry that maps the friendly prop/event surface onto the native node — the same “one core, N adapters” shape used everywhere else in SymbioteNative.
Reference implementation: @symbiote-native/slider
Section titled “Reference implementation: @symbiote-native/slider”packages/slider is the real, shipping example — one wrapper package with a
framework-agnostic core plus a thin entry per adapter:
import { Slider } from '@symbiote-native/slider/react';<script setup>import { Slider } from '@symbiote-native/slider/vue';</script>import { Component } from '@angular/core';import { Slider } from '@symbiote-native/slider/angular';
@Component({ standalone: true, imports: [Slider], template: `<Slider [value]="0.5" />` })export class VolumeControl {}The consuming app installs only @symbiote-native/slider — the wrapper package
itself owns the native dependency, react-native.config.cjs (Android
autolinking proxy), and the iOS podspec, so autolinking discovers it without
the app declaring the third-party native package directly.
Packaging for one-dependency autolinking
Section titled “Packaging for one-dependency autolinking”If you’re authoring your own wrapper (not just consuming Slider), the app
must only ever list @symbiote-native/<lib> — never the third-party native package
directly. That means the wrapper itself has to look autolinkable to React
Native’s CLI on both platforms, even though the real native code lives one
level deeper, inside the wrapper’s own dependency tree.
Android — react-native.config.cjs at the wrapper’s root, resolving the
nested library and pointing sourceDir/libraryName/componentDescriptors
at its real Android sources:
// Must be .cjs — the wrapper package is "type": "module", and a plain// react-native.config.js would be parsed as ESM and silently skipped by the// sync RN CLI config reader.const nativeLibRoot = path.dirname(require.resolve('@react-native-community/slider/package.json'));
module.exports = { dependency: { platforms: { android: { sourceDir: path.relative(cliDependencyRoot, path.join(nativeLibRoot, 'android')), libraryName: 'RNCSlider', componentDescriptors: ['RNCSliderComponentDescriptor'], cmakeListsPath: 'src/main/jni/CMakeLists.txt', }, ios: {}, }, },};iOS — symbiote-<lib>.podspec, resolving the same nested library and
re-exposing its source files and native dependencies. CocoaPods rejects
absolute file patterns, so every path stays relative to the podspec’s own
directory:
native_lib_root = # resolved via Node's require.resolve, same idea as aboves.source_files = File.join(native_lib_relative_root, 'ios/**/*.{h,m,mm}')s.dependency 'React-RCTFabric's.dependency 'React-Codegen'package.json copies the native library’s codegenConfig, but points
jsSrcsDir at the nested copy (node_modules/<native-lib>/src) so RN’s own
codegen still runs against the real spec. The native library itself stays a
regular dependency of the wrapper, never a peerDependency — that’s what
makes it install transitively without the app ever naming it.
Verify autolinking actually discovers it
Section titled “Verify autolinking actually discovers it”Don’t take the packaging on faith — react-native config is the CLI’s own
autolinking resolution, so it’s the ground truth for whether a wrapper is
actually discoverable:
cd examples/react && pnpm exec react-native configThe relevant slice of a real run, trimmed to the wrapper’s own entry:
"@symbiote-native/slider": { "root": ".../examples/react/node_modules/@symbiote-native/slider", "platforms": { "ios": { "podspecPath": ".../node_modules/@symbiote-native/slider/symbiote-slider.podspec" }, "android": { "sourceDir": ".../node_modules/.pnpm/@react-native-community+slider@5.2.0/node_modules/@react-native-community/slider/android", "libraryName": "RNCSlider", "componentDescriptors": ["RNCSliderComponentDescriptor"] } }}That’s the proof the packaging works: ios.podspecPath resolves inside the
wrapper’s own package, while android.sourceDir resolves through to the
real third-party library nested deep in the pnpm store — both discovered
from the app’s single @symbiote-native/<lib> dependency, with the third-party
package never named in the app’s own package.json. If a new wrapper’s
entry is missing entirely, or android/ios comes back null, the proxy
config isn’t wired correctly yet — fix that before touching a simulator.
What this does not solve
Section titled “What this does not solve”If you need the library’s actual React behavior (its stateful component logic) from a non-React adapter, there is no path — that logic runs on the React dispatcher by construction. Wrapping the native view only works because the view itself has no framework dependency; the library’s JS wrapper around it does.
Full package shape, prop-type split, and the autolinking packaging law are in
the project’s
symbiote-third-party-native-view
skill.