Conversation
Port react-native-bottom-tabs to app-modules using Nitro HybridView pattern. iOS implemented with pure UIKit (UITabBarController) instead of SwiftUI, with full iOS 26 liquid glass support including minimizeBehavior, bottomAccessoryView, and tab roles. - Nitro HybridView specs for TabView and BottomAccessoryView - iOS: UITabBarController with UIKit, DelegateProxy pattern, badge colors - Android: wraps existing ReactBottomNavigationView with Nitro bridge - Custom post-nitrogen script for child view management support - JS/TS API consistent with original library for seamless migration Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Test page with 4 tabs (Home, Search, Settings, Profile), SF Symbols, badges with custom colors, and toggleable controls for labeled, translucent, tabBarHidden, sidebarAdaptable, and haptic feedback. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
| // to UITabBarControllerDelegate. | ||
| class DelegateProxy: NSObject, UITabBarControllerDelegate { | ||
| static let shared = DelegateProxy() | ||
| private var mapping: [ObjectIdentifier: HybridTabView] = [] |
There was a problem hiding this comment.
🔴 Memory leak: TabViewDelegateProxy never unregisters container views from static singleton
TabViewDelegateProxy.shared is a static singleton that stores strong references to RCTTabViewContainerView instances in its mapping dictionary (RCTTabViewContainerView.swift:711). Views are registered at line 232 via TabViewDelegateProxy.shared.register(self, for: tbc), but the unregister(for:) method (line 717) is never called anywhere in the codebase. When a RCTTabViewContainerView is removed from the view hierarchy (e.g., navigating away from a screen with tabs), the strong reference in the static mapping prevents deallocation, leaking both the container view and the UITabBarController it retains.
Was this helpful? React with 👍 or 👎 to provide feedback.
| override var hapticFeedbackEnabled: Boolean? = null | ||
| set(value) { | ||
| field = value | ||
| bottomNav.isHapticFeedbackEnabled = value ?: false |
There was a problem hiding this comment.
🔴 Android haptic feedback never triggers because ReactProp sets wrong field
The RCTTabViewManager.setHapticFeedbackEnabled at native-views/react-native-tab-view/android/src/main/java/com/rcttabview/RCTTabViewManager.kt:110 sets view.isHapticFeedbackEnabled, which is the Android View's built-in property. However, ReactBottomNavigationView.emitHapticFeedback() at native-views/react-native-tab-view/android/src/main/java/com/rcttabview/RCTTabView.kt:467 checks the private hapticFeedbackEnabled field (line 71), which is never set by the manager. As a result, haptic feedback is always disabled on Android regardless of the prop value.
Was this helpful? React with 👍 or 👎 to provide feedback.
native-views/react-native-tab-view/android/src/main/java/com/rcttabview/RCTTabView.kt
Show resolved
Hide resolved
Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Wrap Nitro callback props with { f: callback } pattern
- Destructure activeIndicatorColor and convert via colorToString
- Add explicit types for measure callback params
- Use @ts-ignore instead of @ts-expect-error for resolveAssetSource
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
These config files are normally generated by nitrogen but are needed for Metro to resolve the native component registration. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Generated by nitrogen 0.31.10 from TabView.nitro.ts and BottomAccessoryView.nitro.ts. Includes C++, Swift, Kotlin specs, autolinking configs, and JSON view configs. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…space Using ^19.1.0 caused yarn to install a separate react copy in the tab-view module's node_modules, leading to "Invalid hook call" errors. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
| this.items = items | ||
| items.forEachIndexed { index, item -> | ||
| val menuItem = getOrCreateItem(index, item.title) | ||
| if (item.title !== menuItem.title) { |
There was a problem hiding this comment.
🟡 Kotlin reference identity (!==) used instead of structural equality (!=) for String comparison
At RCTTabView.kt:271, item.title !== menuItem.title uses Kotlin's reference identity operator (!==) to compare a String with a CharSequence? (from MenuItem.getTitle()). This checks object identity rather than content equality. Since menuItem.title returns a CharSequence? from the Android framework (which won't be the same object reference as the Kotlin String), this comparison will almost always be true, causing unnecessary title updates on every call to updateItems. The correct operator is != for structural/content comparison.
Was this helpful? React with 👍 or 👎 to provide feedback.
…in tab-view The tab-view package had @types/react: "^18.2.44" which resolved to 18.3.28, conflicting with root's 19.2.7. This caused yarn to create a separate node_modules/ in tab-view with duplicate react-native and react-native-nitro-modules, leading to split ReactNativeViewConfigRegistry singletons. Also removes debug console.logs from TabViewNativeComponent.ts. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Correct nitrogen autolinking filename in podspec (TabViewModule vs react-native-tab-view) - Add missing author and homepage fields to package.json - Update Podfile.lock Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Fix podspec: move pod_target_xcconfig before add_nitrogen_files to prevent overwriting SWIFT_OBJC_INTEROP_MODE setting from nitrogen - Add module_name and PRODUCT_MODULE_NAME to match nitrogen's iosModuleName - Add SWIFT_ENABLE_EXPLICIT_MODULES=NO workaround for Xcode 26 libc++ compat - Fix C++ namespace: TabView:: → TabViewModule:: in HybridTabViewComponent.mm - Fix dictionary literal: [] → [:] in DelegateProxy - Remove RNCTabView dependency from SvgDecoder (use RCTImageDataDecoder only) - Comment out iOS 26 APIs not yet available in SDK (bottomAccessoryView, BottomAccessoryPlacement) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Replace Nitro Modules (HybridView + Nitrogen C++ codegen) with standard React Native ViewManager pattern using requireNativeComponent + interop. - JS: Replace getHostComponent with requireNativeComponent, use NativeSyntheticEvent for events instead of Nitro direct callbacks - iOS: Create RCTTabViewManager (ObjC) + RCTTabViewContainerView (Swift) replacing HybridTabView + Nitrogen generated code - Android: Create RCTTabViewManager with @ReactProp replacing Nitro HybridTabViewSpec, add TabInfo data class and RCTTabViewPackage - Remove all Nitro/Nitrogen dependencies: nitro.json, nitrogen/, CMakeLists.txt, cpp-adapter, post-nitrogen.sh, C++ codegen files - Remove react-native-nitro-modules peer dependency This eliminates C++ compilation entirely, fixing Xcode 26 explicit modules build errors. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Bridging headers are unsupported in framework targets (CocoaPods). Swift files already use `import React` to access RCT types. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
…ut issues - Override didUpdateReactSubviews() to suppress Fabric's re-mounting of child views into the container, which was stealing them from VC views and breaking touch delivery / scroll - Replace Auto Layout constraints (pinEdges) with autoresizingMask for child view attachment, avoiding conflicts with Fabric's direct frame setting that caused layout misalignment on subsequent mounts - Cache UIViewControllers by tab key to avoid unnecessary reparenting - Use function pointer call in RCTTabViewLog (consistent with LCLogger/BTLogger) - Refactor example app routing to use @react-navigation/native headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom headerRight toggle panel with react-navigation's unstable_headerRightItems API using native UIMenu pulldown menu. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Modules are now sorted alphabetically and grouped by first letter with section headers. A floating sidebar allows quick scrolling to any letter via tap or drag. Letters without matching modules are grayed out. Sidebar state updates with search filtering. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Replace card-based layout with grouped list rows, section headers, chevron indicators, and full description text. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Each module now has a distinctive emoji icon displayed on the left side of the row for quick visual identification. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Use concrete TabEvent class to satisfy Event<T extends Event<T>> generic bound, and add null safety for ReadableArray.getMap() return type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes runtime crash "requires your app theme to be Theme.MaterialComponents" by wrapping the context with Theme_MaterialComponents_DayNight. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS keeps native pulldown menu, Android pushes to a settings page with Switch toggles. Both use a shared external store via useSyncExternalStore for instant state sync. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rchitecture) - Replace requireNativeComponent with codegenNativeComponent for both RNCTabView and BottomAccessoryView - Add codegenConfig to package.json for codegen generation - Add C++ common layer: ShadowNode, State, ComponentDescriptor - Android: implement RNCTabViewManagerInterface with codegen delegate, add event classes - iOS: add RCTTabViewComponentView and RCTBottomAccessoryComponentView Fabric views - iOS: change color props from String to UIColor, remove colorFromHex helper - JS: remove colorToString conversion, pass ColorValue directly to native Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use NSClassFromString for Swift class to avoid header dependency issues - Use KVC (setValue:forKey:) for property setting instead of direct access - Use NSInvocation for child mount/unmount to handle NSInteger params - Rename helper to NSStringFromStdStringNilIfEmpty to avoid RN conflict - Add multiple Swift header import fallback paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Swift container's RCTDirectEventBlock callbacks (onPageSelected, onTabLongPress, onTabBarMeasured, onNativeLayout) were never set in Fabric mode, causing events to be silently dropped. This resulted in tab switching not updating JS state, leaving non-home tabs blank. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… .mm file RCTDirectEventBlock is not available in Fabric component view context. Use a local typedef for the same block signature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| const jumpTo = useLatestCallback((key: string) => { | ||
| const index = trimmedRoutes.findIndex((route) => route.key === key); | ||
| onIndexChange(index); | ||
| }); |
There was a problem hiding this comment.
🟡 jumpTo passes -1 index to onIndexChange when route key is not found
In TabView.tsx:332-334, jumpTo uses trimmedRoutes.findIndex() which returns -1 if the key is not found, then unconditionally passes that to onIndexChange(index). This would cause the consumer to receive an invalid index of -1, which would crash when used to access navigationState.routes[-1] (producing undefined) at TabView.tsx:230. The same issue exists in handleTabLongPress at line 340-341 where -1 could be passed to onTabLongPress.
| const jumpTo = useLatestCallback((key: string) => { | |
| const index = trimmedRoutes.findIndex((route) => route.key === key); | |
| onIndexChange(index); | |
| }); | |
| const jumpTo = useLatestCallback((key: string) => { | |
| const index = trimmedRoutes.findIndex((route) => route.key === key); | |
| if (index !== -1) { | |
| onIndexChange(index); | |
| } | |
| }); |
Was this helpful? React with 👍 or 👎 to provide feedback.
- Delete RCTTabViewManager.m and RCTBottomAccessoryViewManager.m (Paper ViewManagers) - Remove #ifdef RCT_NEW_ARCH_ENABLED guards from Fabric ComponentView files - Remove Paper-only methods from Swift container (insertReactSubview, removeReactSubview, didUpdateReactSubviews) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… compatibility The guard in .h files is not for Paper compatibility — it prevents the Swift module compiler from processing C++ header chains (atomic, functional) that are transitively included via RCTViewComponentView.h. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| val inputClass = inputView.inputType and InputType.TYPE_MASK_CLASS | ||
| if (inputClass != InputType.TYPE_CLASS_TEXT) return |
There was a problem hiding this comment.
🔴 autoCorrect/autoCapitalize guards check inputType but setRawInputType does not update inputType
The keyboardType setter now uses inputView.setRawInputType(...) instead of inputView.inputType = .... However, the guards in autoCorrect (line 252) and autoCapitalize (line 272) check inputView.inputType and InputType.TYPE_MASK_CLASS to detect number/phone classes. Since setRawInputType() only changes the content type for the IME without modifying inputType, the getter inputView.inputType still returns the original TYPE_CLASS_TEXT (EditText default). The guard if (inputClass != InputType.TYPE_CLASS_TEXT) return will never skip, so autoCorrect/autoCapitalize will still modify inputView.inputType to text-class values on number/phone inputs — exactly the problem the PR intended to fix.
How to fix
The guard should check the backing field `keyboardType` (the stored prop value) instead of `inputView.inputType`. For example: `if (keyboardType in listOf("numberPad", "decimalPad", "phonePad")) return`.Prompt for agents
In native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt, the guards in the autoCorrect setter (around line 252) and autoCapitalize setter (around line 272) check inputView.inputType to detect number/phone input classes. But since keyboardType now uses setRawInputType() which does NOT update the inputType property, these guards will never trigger.
Fix both guards to check the stored keyboardType field instead:
In autoCorrect setter (line 252-253), replace:
val inputClass = inputView.inputType and InputType.TYPE_MASK_CLASS
if (inputClass != InputType.TYPE_CLASS_TEXT) return
With:
if (keyboardType in listOf("numberPad", "decimalPad", "phonePad")) return
In autoCapitalize setter (line 272-273), replace:
val inputClass = inputView.inputType and InputType.TYPE_MASK_CLASS
if (inputClass != InputType.TYPE_CLASS_TEXT) return
With:
if (keyboardType in listOf("numberPad", "decimalPad", "phonePad")) return
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
react-native-tab-viewnative view module using Nitro HybridView patternreact-native-bottom-tabsfor seamless migrationFeatures
Test plan
useBottomTabBarHeight()displays correct valueGenerated with Claude Code
via Happy