react-native-scrollable-tab-view
react-native-scrollable-tab-view copied to clipboard
tabContainer but no owner was set.
Error: Element ref was specified as a string (tabContainer) but no owner was set. This could happen for one of the following reasons:
- You may be adding a ref to a function component
- You may be adding a ref to a component that was not created inside a component's render method
- You have multiple copies of React loaded See https://reactjs.org/link/refs-must-have-owner for more information.
This error is located at: in AndroidHorizontalScrollContentView (at ScrollView.js:1107) in AndroidHorizontalScrollView (at ScrollView.js:1238) in ScrollView (at ScrollView.js:1264) in ScrollView (at ScrollableTabBar.js:171) in RCTView (at View.js:34) in View (at ScrollableTabBar.js:167) in ScrollableTabBar (at drList.js:2506) in RCTView (at View.js:34) in View (at react-native-scrollable-tab-view/index.js:396) in ScrollableTabView (at drList.js:2504) in RCTView (at View.js:34) in View (at drList.js:2493) in RCTView (at View.js:34) in View (at SafeAreaView.js:41) in SafeAreaView (at AppView.js:12) in AppView (at drList.js:2492) in DrList (created by Connect(DrList)) in Connect(DrList) (at tab3.js:75) in RCTView (at View.js:34) in View (at tab3.js:49) in RCTView (at View.js:34) in View (at SafeAreaView.js:41) in SafeAreaView (at tab3.js:47) in Tab3 (created by Connect(Tab3)) in Connect(Tab3) (created by SceneView) in SceneView (created by TabNavigationView) in RCTView (at View.js:34) in View (created by ResourceSavingScene) in RCTView (at View.js:34) in View (created by ResourceSavingScene) in ResourceSavingScene (created by TabNavigationView) in RCTView (at View.js:34) in View (at src/index.native.tsx:145) in ScreenContainer (created by TabNavigationView) in RCTView (at View.js:34) in View (created by TabNavigationView) in TabNavigationView (created by NavigationView) in NavigationView (created by Navigator) in Navigator (created by NavigationContainer) in NavigationContainer (at home.js:163) in RCTView (at View.js:34) in View (at home.js:157) in Home (created by Connect(Home)) in Connect(Home) (created by SceneView) in SceneView (created by CardContainer) in RCTView (at View.js:34) in View (created by CardContainer) in RCTView (at View.js:34) in View (created by CardContainer) in RCTView (at View.js:34) in View in CardSheet (created by Card) in RCTView (at View.js:34) in View (at createAnimatedComponent.js:217) in AnimatedComponent (at createAnimatedComponent.js:278) in AnimatedComponentWrapper (created by PanGestureHandler) in PanGestureHandler (created by PanGestureHandler) in PanGestureHandler (created by Card) in RCTView (at View.js:34) in View (at createAnimatedComponent.js:217) in AnimatedComponent (at createAnimatedComponent.js:278) in AnimatedComponentWrapper (created by Card) in RCTView (at View.js:34) in View (created by Card) in Card (created by CardContainer) in CardContainer (created by CardStack) in RCTView (at View.js:34) in View (created by MaybeScreen) in MaybeScreen (created by CardStack) in RCTView (at View.js:34) in View (created by MaybeScreenContainer) in MaybeScreenContainer (created by CardStack) in CardStack in KeyboardManager (created by SafeAreaInsetsContext) in SafeAreaProviderCompat (created by StackView) in RCTView (at View.js:34) in View (at GestureHandlerRootView.android.tsx:21) in GestureHandlerRootView (created by StackView) in StackView (created by StackView) in StackView in Unknown (created by Navigator) in Navigator (created by NavigationContainer) in NavigationContainer (created by SceneView) in SceneView (created by CardContainer) in RCTView (at View.js:34) in View (created by CardContainer) in RCTView (at View.js:34) in View (created by CardContainer) in RCTView (at View.js:34) in View in CardSheet (created by Card) in RCTView (at View.js:34) in View (at createAnimatedComponent.js:217) in AnimatedComponent (at createAnimatedComponent.js:278) in AnimatedComponentWrapper (created by PanGestureHandler) in PanGestureHandler (created by PanGestureHandler) in PanGestureHandler (created by Card) in RCTView (at View.js:34) in View (at createAnimatedComponent.js:217) in AnimatedComponent (at createAnimatedComponent.js:278) in AnimatedComponentWrapper (created by Card) in RCTView (at View.js:34) in View (created by Card) in Card (created by CardContainer) in CardContainer (created by CardStack) in RCTView (at View.js:34) in View (created by MaybeScreen) in MaybeScreen (created by CardStack) in RCTView (at View.js:34) in View (created by MaybeScreenContainer) in MaybeScreenContainer (created by CardStack) in CardStack in KeyboardManager (created by SafeAreaInsetsContext) in RNCSafeAreaProvider (at SafeAreaContext.tsx:76) in SafeAreaProvider (created by SafeAreaInsetsContext) in SafeAreaProviderCompat (created by StackView) in GestureHandlerRootView (at GestureHandlerRootView.android.tsx:26) in GestureHandlerRootView (created by StackView) in StackView (created by StackView) in StackView in Unknown (created by Navigator) in Navigator (created by NavigationContainer) in NavigationContainer (created by SceneView) in SceneView (created by SwitchView) in SwitchView (created by Navigator) in Navigator (created by NavigationContainer) in NavigationContainer (at App.js:21) in Provider (at App.js:20) in App (at CodePush.js:585) in CodePushComponent (at renderApplication.js:47) in RCTView (at View.js:34) in View (at AppContainer.js:107) in RCTView (at View.js:34) in View (at AppContainer.js:134) in AppContainer (at renderApplication.js:40)
I am using the latest react-native-scrollable-tab-view
replace the source code by the following node_modules/@react-native-community/viewpager.js `/**
- Copyright (c) Facebook, Inc. and its affiliates.
- This source code is licensed under the MIT license found in the
- LICENSE file in the root directory of this source tree.
- @format
- @flow strict-local */
'use strict';
import type { PageScrollEvent, PageScrollStateChangedEvent, PageSelectedEvent, ViewPagerProps, } from './types';
const React = require('react'); const ReactNative = require('react-native');
const {Platform, UIManager} = ReactNative;
const dismissKeyboard = require('react-native/Libraries/Utilities/dismissKeyboard');
import {childrenWithOverriddenStyle} from './utils';
const NativeViewPager = require('./ViewPagerNativeComponent');
const VIEW_PAGER_REF = 'viewPager'; const VIEW_MANAGER_NAME = 'RNCViewPager';
function getViewManagerConfig(viewManagerName) { if (!UIManager.getViewManagerConfig) { // react-native <= 0.57 return UIManager[viewManagerName]; } return UIManager.getViewManagerConfig(viewManagerName); }
/**
- Container that allows to flip left and right between child views. Each
- child view of the
ViewPager
will be treated as a separate page - and will be stretched to fill the
ViewPager
. - It is important all children are
<View>
s and not composite components. - You can set style properties like
padding
orbackgroundColor
for each - child. It is also important that each child have a
key
prop. - Example:
-
- render: function() {
- return (
-
<ViewPager
-
style={styles.viewPager}
-
initialPage={0}>
-
<View style={styles.pageStyle} key="1">
-
<Text>First page</Text>
-
</View>
-
<View style={styles.pageStyle} key="2">
-
<Text>Second page</Text>
-
</View>
-
</ViewPager>
- );
- }
- ...
- var styles = {
- ...
- viewPager: {
-
flex: 1
- },
- pageStyle: {
-
alignItems: 'center',
-
padding: 20,
- }
- }
-
*/
class ViewPager extends React.Component<ViewPagerProps> { componentDidMount() { // On iOS we do it directly on the native side if (Platform.OS === 'android') { if (this.props.initialPage != null) { this.setPageWithoutAnimation(this.props.initialPage); } } }
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
- when making Flow check .android.js files. */ getInnerViewNode = (): ReactComponent => { return this.pageView.getInnerViewNode(); };
_onPageScroll = (e: PageScrollEvent) => { if (this.props.onPageScroll) { this.props.onPageScroll(e); } // Not implemented on iOS yet if (Platform.OS === 'android') { if (this.props.keyboardDismissMode === 'on-drag') { dismissKeyboard(); } } };
_onPageScrollStateChanged = (e: PageScrollStateChangedEvent) => { if (this.props.onPageScrollStateChanged) { this.props.onPageScrollStateChanged(e); } };
_onPageSelected = (e: PageSelectedEvent) => { if (this.props.onPageSelected) { this.props.onPageSelected(e); } };
/**
- A helper function to scroll to a specific page in the ViewPager.
- The transition between pages will be animated. */ setPage = (selectedPage: number) => { UIManager.dispatchViewManagerCommand( ReactNative.findNodeHandle(this), getViewManagerConfig(VIEW_MANAGER_NAME).Commands.setPage, [selectedPage], ); };
/**
- A helper function to scroll to a specific page in the ViewPager.
- The transition between pages will not be animated. */ setPageWithoutAnimation = (selectedPage: number) => { UIManager.dispatchViewManagerCommand( ReactNative.findNodeHandle(this), getViewManagerConfig(VIEW_MANAGER_NAME).Commands.setPageWithoutAnimation, [selectedPage], ); };
render() { return ( <NativeViewPager {...this.props} ref={pageView=>this.pageView=pageView} style={this.props.style} onPageScroll={this._onPageScroll} onPageScrollStateChanged={this._onPageScrollStateChanged} onPageSelected={this._onPageSelected} children={childrenWithOverriddenStyle(this.props.children)} /> ); } }
module.exports = ViewPager;
scrollabletabBar.js
const React = require('react');
const { ViewPropTypes } = ReactNative = require('react-native');
const PropTypes = require('prop-types');
const createReactClass = require('create-react-class');
const {
View,
Animated,
StyleSheet,
ScrollView,
Text,
Platform,
Dimensions,
} = ReactNative;
const Button = require('./Button');
const WINDOW_WIDTH = Dimensions.get('window').width;
const ScrollableTabBar = createReactClass({ propTypes: { goToPage: PropTypes.func, activeTab: PropTypes.number, tabs: PropTypes.array, backgroundColor: PropTypes.string, activeTextColor: PropTypes.string, inactiveTextColor: PropTypes.string, scrollOffset: PropTypes.number, style: ViewPropTypes.style, tabStyle: ViewPropTypes.style, tabsContainerStyle: ViewPropTypes.style, textStyle: Text.propTypes.style, renderTab: PropTypes.func, underlineStyle: ViewPropTypes.style, onScroll: PropTypes.func, },
getDefaultProps() { return { scrollOffset: 52, activeTextColor: 'navy', inactiveTextColor: 'black', backgroundColor: null, style: {}, tabStyle: {}, tabsContainerStyle: {}, underlineStyle: {}, }; },
getInitialState() { this._tabsMeasurements = []; return { _leftTabUnderline: new Animated.Value(0), _widthTabUnderline: new Animated.Value(0), _containerWidth: null, }; },
componentDidMount() { this.props.scrollValue.addListener(this.updateView); },
updateView(offset) { const position = Math.floor(offset.value); const pageOffset = offset.value % 1; const tabCount = this.props.tabs.length; const lastTabPosition = tabCount - 1;
if (tabCount === 0 || offset.value < 0 || offset.value > lastTabPosition) {
return;
}
if (this.necessarilyMeasurementsCompleted(position, position === lastTabPosition)) {
this.updateTabPanel(position, pageOffset);
this.updateTabUnderline(position, pageOffset, tabCount);
}
},
necessarilyMeasurementsCompleted(position, isLastTab) { return this._tabsMeasurements[position] && (isLastTab || this._tabsMeasurements[position + 1]) && this._tabContainerMeasurements && this._containerMeasurements; },
updateTabPanel(position, pageOffset) { const containerWidth = this._containerMeasurements.width; const tabWidth = this._tabsMeasurements[position].width; const nextTabMeasurements = this._tabsMeasurements[position + 1]; const nextTabWidth = nextTabMeasurements && nextTabMeasurements.width || 0; const tabOffset = this._tabsMeasurements[position].left; const absolutePageOffset = pageOffset * tabWidth; let newScrollX = tabOffset + absolutePageOffset;
// center tab and smooth tab change (for when tabWidth changes a lot between two tabs)
newScrollX -= (containerWidth - (1 - pageOffset) * tabWidth - pageOffset * nextTabWidth) / 2;
newScrollX = newScrollX >= 0 ? newScrollX : 0;
if (Platform.OS === 'android') {
this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false, });
} else {
const rightBoundScroll = this._tabContainerMeasurements.width - (this._containerMeasurements.width);
newScrollX = newScrollX > rightBoundScroll ? rightBoundScroll : newScrollX;
this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false, });
}
},
updateTabUnderline(position, pageOffset, tabCount) { const lineLeft = this._tabsMeasurements[position].left; const lineRight = this._tabsMeasurements[position].right;
if (position < tabCount - 1) {
const nextTabLeft = this._tabsMeasurements[position + 1].left;
const nextTabRight = this._tabsMeasurements[position + 1].right;
const newLineLeft = (pageOffset * nextTabLeft + (1 - pageOffset) * lineLeft);
const newLineRight = (pageOffset * nextTabRight + (1 - pageOffset) * lineRight);
this.state._leftTabUnderline.setValue(newLineLeft);
this.state._widthTabUnderline.setValue(newLineRight - newLineLeft);
} else {
this.state._leftTabUnderline.setValue(lineLeft);
this.state._widthTabUnderline.setValue(lineRight - lineLeft);
}
},
renderTab(name, page, isTabActive, onPressHandler, onLayoutHandler) { const { activeTextColor, inactiveTextColor, textStyle, } = this.props; const textColor = isTabActive ? activeTextColor : inactiveTextColor; const fontWeight = isTabActive ? 'bold' : 'normal';
return <Button
key={`${name}_${page}`}
accessible={true}
accessibilityLabel={name}
accessibilityTraits='button'
onPress={() => onPressHandler(page)}
onLayout={onLayoutHandler}
>
<View style={[styles.tab, this.props.tabStyle, ]}>
<Text style={[{color: textColor, fontWeight, }, textStyle, ]}>
{name}
</Text>
</View>
</Button>;
},
measureTab(page, event) { const { x, width, height, } = event.nativeEvent.layout; this._tabsMeasurements[page] = {left: x, right: x + width, width, height, }; this.updateView({value: this.props.scrollValue.__getValue(), }); },
render() { const tabUnderlineStyle = { position: 'absolute', height: 4, backgroundColor: 'navy', bottom: 0, };
const dynamicTabUnderline = {
left: this.state._leftTabUnderline,
width: this.state._widthTabUnderline,
};
return <View
style={[styles.container, {backgroundColor: this.props.backgroundColor, }, this.props.style, ]}
onLayout={this.onContainerLayout}
>
<ScrollView
ref={(scrollView) => { this._scrollView = scrollView; }}
horizontal={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
directionalLockEnabled={true}
bounces={false}
scrollsToTop={false}
>
<View
style={[styles.tabs, {width: this.state._containerWidth, }, this.props.tabsContainerStyle, ]}
// ref={'tabContainer'}
onLayout={this.onTabContainerLayout}
>
{this.props.tabs.map((name, page) => {
const isTabActive = this.props.activeTab === page;
const renderTab = this.props.renderTab || this.renderTab;
return renderTab(name, page, isTabActive, this.props.goToPage, this.measureTab.bind(this, page));
})}
<Animated.View style={[tabUnderlineStyle, dynamicTabUnderline, this.props.underlineStyle, ]} />
</View>
</ScrollView>
</View>;
},
componentDidUpdate(prevProps) { // If the tabs change, force the width of the tabs container to be recalculated if (JSON.stringify(prevProps.tabs) !== JSON.stringify(this.props.tabs) && this.state._containerWidth) { this.setState({ _containerWidth: null, }); } },
onTabContainerLayout(e) { this._tabContainerMeasurements = e.nativeEvent.layout; let width = this._tabContainerMeasurements.width; if (width < WINDOW_WIDTH) { width = WINDOW_WIDTH; } this.setState({ _containerWidth: width, }); this.updateView({value: this.props.scrollValue.__getValue(), }); },
onContainerLayout(e) { this._containerMeasurements = e.nativeEvent.layout; this.updateView({value: this.props.scrollValue.__getValue(), }); }, });
module.exports = ScrollableTabBar;
const styles = StyleSheet.create({ tab: { height: 49, alignItems: 'center', justifyContent: 'center', paddingLeft: 20, paddingRight: 20, }, container: { height: 50, borderWidth: 1, borderTopWidth: 0, borderLeftWidth: 0, borderRightWidth: 0, borderColor: '#ccc', }, tabs: { flexDirection: 'row', justifyContent: 'space-around', }, }); `
1 node_modules/@react-native-community/js/viewpager.js
line 96
getInnerViewNode = (): ReactComponent => { return this.pageView.getInnerViewNode(); };
line 152
ref={pageView=>this.pageView=pageView}
2 scrollabletabBar.js
line 182
//ref={'tabContainer'}