React Native - React Navigation slow transitions when nesting navigators - react-native

I am building a cross-platform native application using react-native and using react-navigation for navigating to and from screens and managing navigation state using redux. The problem arises when I am nesting my navigators.
For example, I am using Stack Navigator as the default navigator for my app.
export const DefaultNavigate = new StackNavigator(
{
Login: {
screen: LoginScreen,
},
Home: {
screen: AppDrawerNavigate,
},
AppTabNav: {
screen: AppTabNavigator,
},
}
);
where my first screen is loginscreen and home screen is a drawer navigator.
const AppDrawerNavigate = new DrawerNavigator(
{
InProcess: {
screen: InProcess,
},
Machine: {
screen: Machine
},
Settings: {
screen: Settings
},
Logout: {
screen: Logout
},
ContactUs: {
screen: ContactUs
}
}
);
When the user clicks on the Machine in the Drawer Navigator I am navigating the screen to AppTabNav declared in DefaultNavigator.
const AppTabNavigator = new TabNavigator(
{
MachineList: {
screen: MachineList,
},
CalendarView: {
screen: CalendarView,
}
},
);
which is a tab navigator with two screens as the name suggests one is using listview to display list and the other is using the calendarview to display calendar. There are around only 30-40 items in my dataSource of listview so rendering them is a piece of cake for listview. But when there is navigation from any screen to Machine screen from DrawerNavigator there is lag of 1-2sec and js thread drops to -2.1 which is really slowing down the transition.
and if someone need the code for Machine screen in drawer navigator here it is,
componentDidMount() {
if(this.state.loaded)
this.props.navigation.dispatch({ type: MACHINE});
}
render() {
return <AppActivityIndicator />
}
the following is my reducer code which is handling navigation of the screen,
case types.MACHINE:
nextState = DefaultNavigate.router.getStateForAction(
NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Home' }),
NavigationActions.navigate({ routeName: 'AppTabNav' })
]
}),
state
);
the following is the render method of MachineList screen in drawer navigator,
render() {
return (
<View style={styles.container}>
<AppStatusBar />
<ListView
initialListSize={10}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
enableEmptySections={true}
/>
</View>
);
}
Please help me out of this one. What am I doing wrong?
dependemcies
"dependencies": {
"native-base": "^2.3.1",
"react": "16.0.0-alpha.12",
"react-devtools": "^2.5.0",
"react-native": "0.47.1",
"react-native-calendars": "^1.5.8",
"react-native-vector-icons": "^4.3.0",
"react-navigation": "^1.0.0-beta.11",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-persist": "^4.9.1",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
"babel-jest": "20.0.3",
"babel-preset-react-native": "3.0.0",
"jest": "20.0.4",
"react-test-renderer": "16.0.0-alpha.12"
},

I was facing the same issue. There was a substantial delay while switching the screen. I found this very useful blog https://novemberfive.co/blog/react-performance-navigation-animations/
So the problem was
When a new screen is pushed, React Navigation will initially render it off-screen and animate it into place afterward. This means that when a complex screen with lots of components that easily takes a few hundred milliseconds to render is pushed
To fix this, I used InteractionManager. It basically gives you the callback once all the animation has been completed.
Following is what I have done to avoid delay and app was working fine after the fix. Hope this helps.
// #flow
import React, { Component } from 'react';
import { InteractionManager, ActivityIndicator} from 'react-native';
class Team extends Component<Props> {
state = {
isReady : false
}
componentDidMount() {
// 1: Component is mounted off-screen
InteractionManager.runAfterInteractions(() => {
// 2: Component is done animating
// 3: Start fetching the team / or render the view
// this.props.dispatchTeamFetchStart();
this.setState({
isReady: true
})
});
}
// Render
render() {
if(!this.state.isReady){
return <ActivityIndicator />
}
return(
// Render the complex views
)
...
}
}
export default Team;

I was encountering the same issue with my application. Similar to what other people have described, I have nested Stack and Drawer navigators. The lagginess for me was with the transition between screens in a nested Stack Navigator.
I tried using InteractionManager to resolve this, but it did not seem to make much difference. Eventually I found that building a simple timeout in to delay the rendering of large components made a huge difference.
So, I wrote this simple useIsReady hook which I now use in my screens:
import { useEffect, useState } from "react";
const useIsReady = () => {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
setTimeout(() => setIsReady(true), 100);
}, []);
return isReady;
};
export default useIsReady;
This is how I use the hook in my screens:
import React, { memo } from "react";
import { useTheme } from "react-native-paper";
import { View, ActivityIndicator } from "react-native";
import { useIsReady } from "hooks";
const BusyIndicator = () => {
const theme = useTheme();
return (
<View style={{ flex: 1, justifyContent: "center" }}>
<ActivityIndicator size="large" color={theme.colors.primary} />
</View>
);
};
const Camera = () => {
const isReady = useIsReady();
if (!isReady ) {
return <BusyIndicator />;
}
return (
<> ... </>
);
};
export default memo(Camera);
I have found that this has made the world of difference, and my screen transitions are now completely smooth.

i had same problem, for me it helped using NativeStackNavigator instead of StackNavigator

I was facing the same issue. The following steps helped me greatly decrease lag time:
If you wrapped your components (especially the components in BottomTabBar) with redux's compose where you defined your screens, You will do well to remove that. This will greatly improve the smoothness and speed of transitions.
Just as #Carlos has highlighted above, use InteractionManager.runAfterInteractions(()=>{})

Optimize memory usage and performance (v5)
For those who use version 5 of react navigation
You will need to follow the installation instruction from react-native-screens first. After that using the following snippet before your navigation stacks are rendered (typically in an index.js or App.js file):
// Before rendering any navigation stack
import { enableScreens } from 'react-native-screens';
enableScreens();

I was facing the slow tab navigate issue, The slow issues occur when having nesting things like in my case
I have Drawer from react-navigation/drawer FIRST LEVEL
Second I have Tabs from '#react-navigation/bottom-tabs' under the drawer SECOND LEVEL
The third thing I have a stack (it has multiple screens) from #react-navigation/stack under the Tabs THIRD LEVEL
<Drawer.Navigator>
<Tab.Navigator>
<Stack.Navigator>
<Stack.Screen name="Home"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
<Stack.Navigator>
<Stack.Screen name="Task"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
.....
</Tab.Navigator>
</Drawer.Navigator>
So if I remove any one thing from above the slow issue was gone for example if I remove Drawer.Navigator so I have only two nested things Tab.Navigator and Stack.Navigator so issue was gone.
So what I did I remove Drawer and use a drawer from native base(you can use another drawer package).
<Tab.Navigator>
<Stack.Navigator>
<Stack.Screen name="Home"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
<Stack.Navigator>
<Stack.Screen name="Task"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
.....
</Tab.Navigator>
I know how pain is that I suffering it for a long time and didn't find any solution from the official react-navigation team, Hope Team will fix nesting issue soon, for now, you can use that solution.

Pass a callback function to predefined requestAnimationFrame(no import needed) method and it will automatically invoke that function once the animation is completed.
requestAnimationFrame(() => {
//enter code here
}

I am also facing this issue with stack navigator, Drawer navigator works better..
in stack if there're content of more than 100 lines with some imported component it start's lagging and delay on navigate.
i used in one of my stack screen InteractionManager to render component after animation
it worked, but when i come back on click back, animation shutters/lags
i checked out https://reactnavigation.org/docs/react-native-screens/
as it says react-navigation already uses native navigation component
for disabling and using RN View :-
import {enableScreens} from 'react-native-screens'
enableScreens(false)
i disabled it and now its working way better than native one, dont know why, currently i am in debugging mode, so cant say if it's cause of the remote view...
and really confused why it lags on native navigation while not in RN View
can anyone let me know why its happening? i want to use native navigation

Related

How to add transition while navigating from one screen to other screen using react-navigation

I am using react-navigation to navigate from one screen to another screen. But while navigating I can see the navigation not good enough. So I am trying to add the transition while navigating from a screen to another screen. Any solution for this using react-navigation
if you want to just use react-navigation then use the following link
you will have to use Transitioner
class MyNavView extends Component {
...
render() {
return (
<Transitioner
configureTransition={this._configureTransition}
navigation={this.props.navigation}
render={this._render}
onTransitionStart={this.onTransitionStart}
onTransitionEnd={this.onTransitionEnd}
/>
);
}
or
You can use the following third party library for the Transition in navigation
Click Here
import { createStackNavigator } from 'react-navigation';
import { fromLeft } from 'react-navigation-transitions';
const appStack = createStackNavigator(
{
ScreenA: {
screen: ScreenA,
},
ScreenB: {
screen: ScreenB,
},
},
{
initialRouteName: 'ScreenA',
transitionConfig: () => fromLeft(),
},
);

React native react-navigation drawer not opening

I am using react-navigation as the navigation package for my react native application. and have also installed and configured react-native-gesture-handler along with react-navigation as mentioned in the documentation.
The problem i am facing is that the drawer doesn't open at random times. mostly this occurs when user goes through along the main stack navigation and comes back to home to open the drawer. otherwise the drawer seems to be working without any issues.
this is how i have configured my navigation,
MAIN STACK NAVIGATION
const AppStack = createStackNavigator(
{
DrawerNav: DrawerNav,
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
DRAWER NAVIGATION
const MyDrawerNavigator = createDrawerNavigator(
{
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
},
And the MAIN STACK also contains a few TAB STACK also,
I want to know why the drawer doesn't respond.
The Code i used to open the drawer was
this.props.navigation.openDrawer();
bu the above code gave
this.props.navigation.openDrawer() undefined
when ever the above crash i mentioned occurs
as a fix i used,
import { DrawerActions } from "react-navigation";
this.props.navigation.dispatch(DrawerActions.openDrawer())
the above code also stop working after a the user goes through the STACK navigation a few times, but doesn't give any errors on development.
This error occurs both on production as well as development
currently running
react native : 0.59.8
react : 16.8.3
react navigation: 3.9.1,
react-native-gesture-handler:1.1.0,
any help would be much appreciated,
Thanks in advance
Try wrapping all your stack navigation with Drawer navigation.
const StackNav = createStackNavigator({
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
Now wrap the above with Drawer navigation
const AppStack = createDrawerNavigator({
StackNav: {
screen: StackNav,
navigationOptions: {
drawerLabel: <Hidden />,
},
},
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
});
Now the StackNav will be showing in the Drawer as one of the screens. So create a class and return null then pass it down to Drawer label.
class Hidden extends Component {
render() {
return null;
}
}
Now you'll be able to call the this.props.navigation.openDrawer(); anywhere on the app. Let me know if it works.
I think you can use another easy way to handle this problem :
You can use react-native-drawer that is available in this link now i'm going to show you how you can work with it :
AppStack :
const AppStack = createStackNavigator(
{
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
Home Navigation
const MyHomeNavigator = createStackNavigator(
{
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
},
Now lets assume this is your HomePage :
HomePage
import Drawer from 'react-native-drawer'
import DrawerComponent from '../components/drawer'
export default class HomePage extends Component{
render() {
return (
<Drawer ref={(ref) => this._drawer = ref} content={<DrawerComponent {...this.props}/>} side='right' captureGestures openDrawerOffset={0.3} acceptTap>
//your home page components
</Drawer>
)
}
}
as you can see you can access to the drawer by this._drawer and here i will show you how does <DrawerComponent> like :
DrawerComponent :
export default class DrawerComponent extends React.Component {
navigateTo = (path) => {
this.props.navigation.navigate(path)
}
render(){
return(
<View>
<View>
<Item path='MyAccount' navigate = {this.navigateTo}/>
<Item path='ContactUs' navigate = {this.navigateTo}/>
<Item path='InviteFriend' navigate = {this.navigateTo}/>
//And you add all the item you need here with navigateTo function
</View>
<View>
//LogOut Section
</View>
</View>
)
}
}
This Works for me fine, I hope this works for you too.
Best regards .

What am i doing wrong with this stack navigator?

I am trying to teach myself react-native by building a new app, using Firebase as the backend. Currently I'm trying to get my head around react-navigation navigators to lay out the screens on my app before i add content.
The issue I'm having is that the stack navigator doesn't navigate. It renders the next screen, and adds it to the page, and then initiates the transition - but for whatever reason the transition just doesn't happen.
so the following events fire:
* willBlur (on the originating screen)
*onTransitionStart` (on the stack transition)
I've assumed i've done something wrong, so i've rewritten three or four times, and this final time i've boiled it down to the simplest possible iteration of the problem, just a stack navigator and two screens - and i'm getting the same issue.
I've gone through the documentation a few times, and i think at this point i've just been a massive idiot about this.
This is my entire app to show it
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { createAppContainer, createStackNavigator } from 'react-navigation';
class Main extends React.Component {
navigateToList() {
const { navigation } = this.props;
navigation.navigate('list');
}
render() {
return (
<TouchableOpacity
onPress={this.navigateToList.bind(this)}
>
<Text>Go To Listing Page</Text>
</TouchableOpacity>
);
}
}
class List extends React.Component {
render() {
const { refreshing } = this.props;
return (
<Text>LISTING PAGE</Text>
);
}
}
const MainStack = createStackNavigator({
mode: { screen: Main },
list: { screen: List }
}, {
initialRouteName: 'mode',
});
const App = createAppContainer(MainStack);
export default () => (
<App />
);
and my package.json has the following:
"react": "16.8.3",
"react-native": "0.59.3",
"react-native-firebase": "^5.4.0",
"react-native-gesture-handler": "^1.3.0",
"react-navigation": "^3.11.0"
At the moment this just sits showing the "main" screen, and doesn't navigate to the "list" screen. Also the button stops responding, so it looks like things start working but then never complete for some reason i can't explain.

Snapchat-like Horizontal ScrollView Combined with React Navigation

I'm trying to create a ScrollView similar to Snapchat's frontend, with 3 screens. However, I want to be able to use react-navigation's ability to set an initial route so that every time the home screen loads, it starts on the middle screen instead of the left-most screen.
This is for react-native version: 0.57.1, and react-navigation version: 3.0.9. This works fine when I put all 3 screens in the ScrollView component directly, but then I can't find a way to select which screen is displayed initially.
Here is something similar to what I'm aiming for:
const HomeScreenStack = createStackNavigator({
Goals: GoalsScreen,
Dash: DashScreen,
Plans: PlansScreen,
}, {
initialRouteName: "Dash"
})
class HomeScreen extends React.Component {
render() {
return (
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={true}>
<HomeScreenStack />
</ScrollView>
)
}
}
I expect HomeScreenStack to display all 3 routes within a ScrollView, but I get an error saying:
Invariant Violation: Invariant Violation:
The navigation prop is missing for this navigator.
In react-navigation 3 you must set up your app container directly.
More info: https://reactnavigation.org/docs/en/app-containers.html
Rather than using a ScrollView, you could take advantage of react-navigation's createMaterialTopTabNavigator and nullify the tabBarComponent. It has swipe navigation enabled by default.
You also need to set up an app container for react-navigation, which is where that error is coming from.
Note that AppContainer should be used only once and in the main entry point file, such as App.js.
import { createAppContainer, createMaterialTopTabNavigator } from 'react-navigation'
const HomeNavigator = createMaterialTopTabNavigator(
{
Goals: GoalsScreen,
Dash: DashScreen,
Plans: PlansScreen,
},
{
initialRouteName: "Dash",
tabBarComponent: null
}
)
const AppContainer = createAppContainer(HomeNavigator)
class HomeScreen extends React.Component {
render() {
return (
<AppContainer />
)
}
}

Unmount or re-render screen in drawer navigator

I just added drawer navigator recently and wrapped my screens in createDrawerNavigator()
These are my current routes:
Home.js
Post.js
Settings.js
When a user navigates from Home to Post I pass a param that has the post data.
onPress={() => this.props.navigation.navigate('Post', {postData: postData})}
When ever the user goes back to Home from Post, then post will be unmounted. and mounted back again with fresh data when another post is clicked.
My issue is that with implementing the drawer, the Post screen does not get unmounted when navigating back to home, I keep gettings the same props and screen of the first post opened, over and over again.
This is my route setup:
import React from "react";
import { createDrawerNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
import Home from './screens/Home';
import Post from './screens/Post';
import Settings from './screens/Settings';
import SideBar from './screens/sidebar';
const Drawer = createDrawerNavigator(
{
Home: {screen: Home},
Post: {screen: Post},
Settings: {screen: Settings}
},
{
initialRouteName: "Home",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
const AppNavigator = createStackNavigator(
{
Drawer: {screen: Drawer},
},
{
initialRouteName: "Drawer",
headerMode: "none",
}
);
export default createAppContainer(AppNavigator);
What am I doing wrong?
I want each post screen to open and re render as new when navigating to it from Home.
For those using react-navigation v5. I faced the same issue my component was not unmounting by using goBack() for a screen link in drawer. After little research I found our that in latest version of react-navigation they have added an attribute unmountonblur in screen options for drawer navigator by default its false that's why component doesn't unmount. I am using this to solve my problem.
Here is my code
<Drawer.Screen name="ResetLogsStack" component={ResetLogsStack} options={{unmountOnBlur:true}}/>
Here is the link for that: unmountonblur
I use to face the same issue. I made my screen Post listen to navigation focus event triggered by react-nativation here instead of componentDidMount.
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const Post = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus',payload)}
onDidFocus={payload => console.log('did focus',payload)} //
onWillBlur={payload => console.log('will blur',payload)}
onDidBlur={payload => console.log('did blur',payload)}
/>
{/*
Your view code
*/}
</View>
);
With onDidFocus, you may get the navigation param, fetch data, and/or update state. You may clear screen state with onDidBlur if needed.
Alternatively, you can do imperative coding as this doc here
Update :
By the way, I am wondering why you put Post with Drawer? Is it just to have a link in the drawer that can access to the Post page?
In my opinion, you should move Home and Post to new stack and make Home as initial Route. This will make sure that the Post is unmounted after navigating back to Home.
Check out my sample below
const HomeStack = createStackNavigatior({
Home: {screen: Home},
Post: {screen: Post},
}, {
initialRouteName: 'Home',
...
})
const Drawer = createDrawerNavigator(
{
HomeStack
Settings: {screen: Settings}
},
{
initialRouteName: "HomeStack",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
It's the way react-navigation works, to counter this you can add listeners, like so:
<NavigationEvents
onDidFocus={payload => { console.log(payload.state.params,'<- this has your new params') }}
/>
OR
this.props.navigation.addListener('didFocus', payload=>{console.log(payload)})
Check this for more information
I lost several hours on this after updating React Navigation from 2.14.2 to 3.11.0. The funny thing is that it was working perfectly and after upgrade pages were not re-rendering which was causing to wrong language and not actual data on the pages.
What I have found is the issue DrawerNavigator screen shouldn't unmount
So part of us complain why it is not rerendering screens and part was complaining why it was rerendering. The second part won the battle :)
Basically the solution was as expected: to listen to WillFocus event...
Just add this to code to your component if you want it to be rerendered on each visit.
/**
* When using React Navigation Component is not umnounted and instead stored in navigator.
* We need component to be rerendered during each visit to show right info and language
*/
componentDidMount() {
//this.props.navigation will come in every component which is in navigator
focusSubscription = this.props.navigation.addListener(
'willFocus',
payload => {
this.forceUpdate();//Native react function to force rerendering
}
);
this.setState({focusSubscription: focusSubscription});
}
componentWillUnmount() {
this.state.focusSubscription.remove();
}
I tried to use the method suggested in the answers but they didn't work perfectly with my case.
Solution:
I tried to flip createDrawerNavigator() with createStackNavigator() and it worked perfectly like before!
I don't have to use react navigation events, normal event like componentWillUnmount and componentWillMount work perfectly.
const MainScreens = createStackNavigator(
{
Home: {screen: Home},
Post: {screen: Post},
Settings: {screen: Settings}
},
{
initialRouteName: "Home",
headerMode: "none",
}
);
const AppNavigator = createDrawerNavigator(
{
Main: {screen: MainScreens},
},
{
initialRouteName: "Main",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
export default createAppContainer(AppNavigator);
Not sure if there's anything wrong in what I did, but its working fine until now.
As per Muhammad Zain response, if you are using react navigation v5, you can pass directly to the Drawer screen the option unmountOnBlur
options={{unmountOnBlur:true}}
and that will be enough.
React navigation docs
I'm using react-navigation for months. But placing screens directly into Drawer navigator prevents willunmount method to be called. First place screen in a stack navigator then place it into drawer navigator.