Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"
},
like image 625
Shivansh Singh Avatar asked Sep 09 '17 06:09

Shivansh Singh


2 Answers

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;
like image 174
Jitender Avatar answered Oct 20 '22 22:10

Jitender


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.

like image 29
BruceHill Avatar answered Oct 20 '22 22:10

BruceHill