Shivansh Singh
Shivansh Singh

Reputation: 322

React Native - React Navigation slow transitions when nesting navigators

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.This drop is frequent whenever I am nesting tab navigator within drawer navigator

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"
},

Upvotes: 18

Views: 32329

Answers (9)

rohit
rohit

Reputation: 29

import { useCallback, useState } from 'react'
import { InteractionManager } from 'react-native'
import { useFocusEffect } from 'expo-router'

const useIsReady = () => {
  const [isReady, setIsReady] = useState(false)

  useFocusEffect(
    useCallback(() => {
      InteractionManager.runAfterInteractions(() => {
        setTimeout(() => setIsReady(true), 100)
      })

      return () => {
        setIsReady(false)
      }
    }, [])
  )

  return isReady
} 

export default useIsReady

this is modified version of @BruceHill answer for expo

Upvotes: 1

Mohammadreza Khedri
Mohammadreza Khedri

Reputation: 2691

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();

Upvotes: 1

Hamza Waleed
Hamza Waleed

Reputation: 1462

Pass a callback function to predefined requestAnimationFrame(no import needed) method and it will automatically invoke that function once the animation is completed.

requestAnimationFrame(() => {
   navigation.navigate('routeName') //enter code here
}

Upvotes: 1

BruceHill
BruceHill

Reputation: 7164

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.

Upvotes: 7

mradex77
mradex77

Reputation: 137

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

Upvotes: 2

Vivek sharma
Vivek sharma

Reputation: 53

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

Upvotes: 0

LordKiz
LordKiz

Reputation: 643

I was facing the same issue. The following steps helped me greatly decrease lag time:

  1. 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.

  2. Just as @Carlos has highlighted above, use InteractionManager.runAfterInteractions(()=>{})

Upvotes: 1

Saad Qamar
Saad Qamar

Reputation: 1099

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.

Upvotes: 0

Jitender
Jitender

Reputation: 7971

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;

Upvotes: 23

Related Questions