React Native - React Navigation slow transitions when nesting navigators

20,517

Solution 1

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;

Solution 2

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.

Solution 3

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(()=>{})

Solution 4

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

Share:
20,517

Related videos on Youtube

Shivansh Singh
Author by

Shivansh Singh

Updated on February 11, 2022

Comments

  • Shivansh Singh
    Shivansh Singh about 2 years

    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"
    },
    
    • Sara Inés Calderón
      Sara Inés Calderón about 6 years
      did you ever find a solution?
    • Shivansh Singh
      Shivansh Singh about 6 years
      no .. but a work around worked .. I delayed the rendering of component by 10ms using timeout function on componentWillMount .. it still shows JS thread drop but UI doesn't get affected due to it. Maybe it is bad coding or react navigation nesting issue.
  • Shivansh Singh
    Shivansh Singh about 5 years
    I don't have the means to confirm your answer but I also think this will work as I said delay of 10ms before pushing the screen fixed the lag so it is also doing same. Thanks for the answer.
  • TheEhsanSarshar
    TheEhsanSarshar almost 5 years
    awesome solution
  • Oliver D
    Oliver D about 4 years
    Hey @Carlos should i use InteractionManager.runAfterInteractions(()=>{}) in every single Screen, I'm faced slow transition issue on it?
  • Jitender
    Jitender about 4 years
    @OliverD: I would say use it where you have your routes defined.
  • Oliver D
    Oliver D about 4 years
    @Carlos Interest!, I'm open an issue here and someone else faced the same issue maybe it's a bug?
  • Vivek sharma
    Vivek sharma almost 3 years
    well it worked for me, have you tried interactionManager?
  • BruceHill
    BruceHill about 2 years
    The blog mentioned in this answer does not exist any longer. Here is the wayback machine link to it: web.archive.org/web/20201127164304/https://www.novemberfive.‌​co/…