How do I call navigator from outside the Navigator? - react-native

I have a tab bar in my application that I don't want the navigation transition to affect each time a new route is rendered. Therefore I want to place the tab bar outside the Navigator, but how do I trigger the navigation actions in that case? I cannot access the navigator object that is passed to the renderScene function from outside the Navigator.
Here is what is returned in app.js:
<View>
<Navigator
ref="navigator"
initialRoute={{name: "start"}}
renderScene={this.renderScene.bind(this)}
configureScene={this.configureScene.bind(this)}
/>
<TabBar navigator={???} style={styles.nav} />
</View>

Ok, I think I solved this. The problem seems to be that the refs is not available at the first render of the component. I solved this by:
<Navigator
ref={(r) => { this.navigatorRef = r; }}
initialRoute={{name: "start"}}
renderScene={this.renderScene.bind(this)}
configureScene={this.configureScene.bind(this)}
/>
{this.state ? <TabBar navigator={this.state.navigatorRef} style={styles.nav} /> : null }
and added the this:
componentDidMount() {
this.setState({navigatorRef: this.navigatorRef});
}
just to make the component render a second time with the TabBar.

I don't think this is the best way to do what you want. But to answer your question:
<View>
<Navigator
ref="navigator"
initialRoute={{name: "start"}}
renderScene={this.renderScene.bind(this)}
configureScene={this.configureScene.bind(this)}
/>
<TabBar getNavigator={()=>this.refs.navigator} style={styles.nav} />
</View>
And then you could call getNavigator() from your TabBar

Here's another version that worked for me. It is hackish and will probably break in the future but might help if you're in a hurry ;)
<View>
<Navigation
ref={(r) => { this.navigation = r }}
/>
<OtherScreen
navigation={(this.navigation || {})._navigation}
{...this.props}
/>
</View>

Related

React Native - VirtualizedLists should never be nested inside plain ScrollViews with the same orientation

I'm working on an app for existing web application with expo cli.
I have completed the home screen but I'm getting this warning when using ScrollView
VirtualizedLists should never be nested inside plain ScrollViews with
the same orientation - use another VirtualizedList-backed container
instead.
I have a folder screens where I've all the pages of the website. so currently I have Home.js where I have different sections.
<View>
<Showcase />
<StepsList />
<HomeInfo />
<HomeCorporateMenu />
<HomeMonthlyMenus />
</View>
Then rendeing this component in the root component App.js. but I was not able to scroll down to see other sections.
So I've added SafeAreaView and ScrollView components. now I'm able to scroll but sometimes I get that warning in the console.
I have looked it up SO and found some solutions.
Added style={{ flex: 1 }} and keyboardShouldPersistTaps='always' to ScrollView component
But Still getting the same warning. Any suggestions?
<SafeAreaView style={styles.container}>
<ScrollView>
<Header />
<HomeScreen />
</ScrollView>
</SafeAreaView>
The warning is, obviously, telling you that you shouldn't nest multiple VirtualizedList (FlatList and SectionList) components with the same orientation (the orientation of your lists is probably vertical).
To do it properly, and for the warning to disappear, you have to use the ListHeaderComponent or ListFooterComponent prop of the VirtualizedList and nest them like this:
const App = () => {
const ListFooterComponent = (
<>
<FlatList
// other FlatList props
data={stepsListData}
/>
<FlatList
// other FlatList props
data={homeInfoData}
/>
<FlatList
// other FlatList props
data={homeCorporateMenuData}
/>
{/* ... */}
</>
);
return (
<FlatList
// other FlatList props
data={showcaseData}
ListFooterComponent={ListFooterComponent}
/>
);
};
Or another possible workaround (and not an official resolution) would be to use a FlatList with empty data and renderItem props instead of using a ScrollView. Instead of this:
const App = () => {
return (
<SafeAreaView style={styles.container}>
<ScrollView>
<Header />
<HomeScreen />
</ScrollView>
</SafeAreaView>
);
};
You can use this:
const emptyData = [];
const renderNullItem = () => null;
const App = () => {
const ListFooterComponent = (
<>
<Header />
<HomeScreen />
</>
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={emptyData}
renderItem={renderNullItem}
ListFooterComponent={ListFooterComponent}
/>
</SafeAreaView>
);
};

Tab navigation lag when nested in drawer

I am having a issue when I am nesting my Tabs within my Drawer. Unfortunately, navigating to each tab is very slow, and their seems to be a lot of lag.
However, when I remove the Drawer navigator, and make it so that their is only a tab navigator, navigating between the different tab screens is noticeably better.
How can I make it so that their is no delay between the tabs when the tabs are nested in to the drawer?
{ *
With help from Mateusz, I have managed to pinpoint the issue. I tested the delay by rendering four of the same components. The first test was using
children={() => {
return <NfcWifiConfig />;
}}
And the delay was still there
But then, when I used
component={NfcWifiConfig}
The delay is completely gone and navigation is running smoothly as it should. So my question now is, where do i go from here? How would i pass the props down with this syntax?
}
My current code is:
const DrawerComponent = ({
Bunch of props here
}) => {
return (
<Drawer.Navigator
drawerType="back"
drawerContent={(props) => {
return (
<DrawerContent
{...props}
/>
);
}}
>
{/* TABS */}
<Drawer.Screen
name="MainHome"
children={({navigation}) => {
return (
<>
<StatusBar backgroundColor={homeColor} barStyle="dark-content" />
<Navbar navigation={navigation} userimage={userimage} />
<Tabs.Navigator>
{/* HOME STACK */}
<Tabs.Screen
name="Profile"
children={() => {
return (
<>
<MainStackNavigator
{Bunch of props here}
/>
</>
;
}}
/>
{/* SEARCH SCREEN */}
<Tabs.Screen
name="Search"
children={() => {
return (
<>
<StatusBar barStyle="dark-content" />
<SearchStack
{ Bunch of props here }
/>
</>
);
}}
/>
{/* NFC-SOCIAL SCREEN */}
<Tabs.Screen name="Activate" component={NfcConfig} />
{/* NFC-WIFI SCREEN */}
<Tabs.Screen name="WiFi" component={NfcWifiConfig} />
</Tabs.Navigator>
</>
);
}}
/>
{/* Add Links Screen */}
<Drawer.Screen
name="Add Links"
children={({navigation}) => {
return (
<AddLinksScreen
{ Bunch of props here }
/>
);
}}
/>
{/* Following Screen */}
<Drawer.Screen
name="Followers"
children={({navigation}) => {
return (
<FollowerStack
{ Bunch of props here }
/>
);
}}
/>
{/* Following Screen */}
<Drawer.Screen
name="Following"
children={({navigation}) => {
return (
<FollowingStack
{ Bunch of props here }
/>
);
}}
/>
</Drawer.Navigator>
);
};
Also, the add links screen and followers/following screens work fine. Navigating to them works efficiently with no lag. But the tabs => home stack, search screen and the other two, have a heavy delay when navigating between them.
In terms of the content inside the tabs, the last two tabs are very light, and do not contain much content. I have tried commenting out the heavy tab screens and using just the two lightweight components, but same result. Making me believe that is not the issue.
So I managed to fix the issue. When I used:
children={() => {
return <NfcWifiConfig props{props} />;
}}
The lag was present. However, when I used:
component={NfcWifiConfig}
The lag disappeared. However, my props were not being passed through. So what I did was use React Context to pass my props to all the different components that needed it and that's it, the lag was gone and the components were receiving the props as I wanted.
Also, the code is a lot cleaner when using React context, so I highly recommend it.

React Navigation (Native): custom header

I'm having an issue with the react-navigation's drawer component not covering the whole application. I'm really struggling as I'm new with React Native and can't figure out the "clean way" to do it
Here's an example:
https://snack.expo.io/ZZqxmOQMw
When you press on "Toggle drawer" I expect it to cover the whole app, including the header, but it only covers the main content. On their examples, the drawer always only work with no content nor header.
Thank you!
I think this is a simple solution for you.
function withHeader(Component) {
return function(props) {
return (
<>
<Header />
<Component {...props} />
</>
)
}
}
function MyDrawer() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Feed" component={withHeader(Feed)} />
<Drawer.Screen name="Notifications" component={withHeader(Notifications)} />
</Drawer.Navigator>
);
}
function Header() {
return <View style={{height: '50px', backgroundColor: 'red'}}>
<Text>My custom header!</Text>
</View>
}
export default function App() {
return (
<NavigationContainer>
<MyDrawer />
</NavigationContainer>
);
}
I think you should move the Header component to each screen or using the stack inside the drawer to create header

React Native ScrollView won't swipe with ViewPager or createMaterialTopTabNavigator

I have been researching this for days after days and still didn't get what I want.
I don't even know why this has not been raised yet..
For example, if you use
const Tab = createMaterialTopTabNavigator();
from #react-navigation/material-top-tabs
export class TopTab extends React.Component {
render() {
return (
<Tab.Navigator
swipeEnabled={false}
tabBar={(props) => <CustomTabBar {...props} />}
>
<Tab.Screen name="First" component={FirstView} />
<Tab.Screen name="Second" component={SecondView} />
<Tab.Screen name="Third" component={ThirdView} />
</Tab.Navigator>
);
}
export class FirstView extends React.Component {
render() {
return (
<ScrollView horizontal>
.... contents
</ScrollView>
);
}
then ScrollView horizontal swipe won't work within these Views.
what I've tried:
swipeEnabled=true
nestedScrollViewEnabled=true
Is there any known solution to this?
I've tried pager option to customize ViewPaging with react-native-community/viewpager, still no luck.
Please help!
Ah, feels stupid now. The problem wasn't actually neither ScrollView nor Navigator.
It was Touchable components masking scrolling behaviour.

Adjacent JSX elements must be wrapped in an enclosing tag

I want to have a navigation bar at the bottom and a toolbar at the top of every page in my React-Native app. However, if I create these two elements in the index.ios.js file, I get the error: "Adjacent JSX elements must be wrapped in an enclosing tag". If I put tags surrounding the Navigator and NavBar I just see a blank screen in my app. What should I do? Here is what my render function looks like in index.ios.js
render() {
return (
<Navigator
initialRoute={{name: 'Login'}}
renderScene={this.renderScene}
navigationBar={
<Navigator.NavigationBar
style={ styles.nav }
routeMapper={ NavigationBarRouteMapper } />
}
/>
<NavBar />
);
}
As per React 16, if you wan't to avoid the <View> wrapping,
you can return multiple components from render as an array.
return ([
<Navigator key="navigator" />,
<NavBar key="navbar" />
]);
Or in a stateless ES6 component:
import React from 'react';
const Component = () => [
<Navigator key="navigator" />,
<NavBar key="navbar" />
];
export default Component;
Don't forget the key property as React needs (as for iteration on Array) to be able to discriminate your components.
Edit (Nov. 2018)
Your can also use React.Fragment shorthand:
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
When you wrap it in a view make sure you set the flex to 1. My guess is that the view you are giving it has no size and therefore the child elements are inheriting their size from the parent (which is 0)
Wrap both inside a View and give that outer View wrapper a style of flex 1. Example:
<View style={{flex: 1}}>
<Navigator
{. . .}
/>
<NavBar />
</View>