kirillzyusko / react-native-bundle-splitter

HOC for lazy components loading

Home Page:https://kirillzyusko.github.io/react-native-bundle-splitter/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

lazy load nested stack navigator

MaxToyberman opened this issue · comments

Describe the bug
In my app i have multiple stack navigators, when i try to use register on the stack it doesnt work, but when i do it on screens separately it works.

Code snippet

import { FTUEStack } from './FTUEStack';

const AppNavigator = createSwitchNavigator({
    FTUE: {
      screen: FTUEStack
    }
)

changing it to (doesn't work):

import { register } from 'react-native-bundle-splitter';

const AppNavigator = createSwitchNavigator({
    FTUE: {
      screen: register({
           require: () => require('./FTUEStack'),
           name: "FTUE"
      })
    }
)

i preload the component and then navigate, i have no errors but i dont have navigation.

Thank you for your help !

@MaxToyberman do you use react-navigation 4?

FTUEStack is a stack navigator.
i tried to debug it and i saw that OptimizedComponent render is not called.
when i use register inside the stacknavigator is works

@MaxToyberman I'm afraid, that createStackNavigator may not return React component (but I may be wrong). Anyway, I have two suggestions, that you may try:

Option 1: try to use register outside of switch navigator. Code should look like that:

import { register } from 'react-native-bundle-splitter';

const FTUEStack = register({
     require: () => require('./FTUEStack'),
     name: "FTUE"
});

const AppNavigator = createSwitchNavigator({
    FTUE: {
      screen: FTUE
    }
)

Option 2: specify group for all screens that are nested in one navigator. In this case all navigators will be loaded at first run of app. But actual screens, that should be displayed will not be loaded until you decide to preload them (preload().group("FTUEStack")).

@kirillzyusko thank you for your response.
createStackNavigatoris a function that returns react comopnent.

Option 1: doesn't work.

have no choice will use option 2.

Thanks!

@MaxToyberman then it's strange, why it doesn't work and doesn't show any errors 🤔

@kirillzyusko FTUEStack is OptimizedComponent

image

@MaxToyberman would you mind to create a small reproducible demo?

In this case I can have a look on that and understand what happens.

Originally, this library was created for lazy loading only of screens (and not orchestrators of screens such as like Stack/Drawer/Bottom-navigator). This modular approach allowed this library to be used in combination with various navigation libraries.

But I still think that it should work even with entire stacks (not single screen). So I'd highly appreciate you if you could create small reproducible demo :)

Hi @kirillzyusko

created a new sample app, this time i have an error "no routes in navigation state".
i agree that it is made for screens, but loading a whole stack can save a lot of time.

here is a link.

https://github.com/MaxToyberman/bundleSplitterExample

@MaxToyberman thanks, I will have a look on that!

Hello @MaxToyberman

Thanks for the issue that you raised (and especial thanks to sample repo)

Unfortunately, you can not do lazy loading for entire stacks in react-navigation v4. It's a limitation of a library itself. This library needs to know all possible routes in advance in order to understand, which component should be shown. When you do a lazy loading - you cut off part of routes. And even if you preload them later - react-navigation v4 will not see them, since initialization stage is done on first execution.

Unlike react-navigation v5. In this version authors of library significantly reworked the core ideas and starting from this version you can dynamically add/delete routes.

In your case I see several ways for dealing with this issue:

  • if you don't want to migrate to react-navigation v5, then you will need to wrap all screens into register function and preload them when it's necessary (it's better to use preload().group() for such purposes);
  • and the second option is migrating to react-navigation v5 :) I'l attach a patch to your codebase to provide real working example

Personally me - I would prefer to migrate to react-navigation v5. Usage of this library may open a new horizons for you as for developer. But at the same time I understand, that on large projects it may be hard to do, since it may break a lot of existing functionality (I had an experience with migrating from v4 to v5 and we spent more than a week! But we haven't used compat layer - we decided to rewrite everything literally from the scratch, since in our old navigation we had a lot of issues). But everything is up to you!

I hope I gave you an overview of what can be done and provided an explanation why it will not work as you want now.

Patch to yours example is below.

// App.js
   DebugInstructions,
   ReloadInstructions,
 } from 'react-native/Libraries/NewAppScreen';
+import { NavigationContainer } from '@react-navigation/native';
 
 import AppNavigator from './router';
 
const App: () => React$Node = () => {
   return (
     <>
       <StatusBar barStyle="dark-content" />
-      <AppNavigator />
+      <NavigationContainer>
+        <AppNavigator />
+      </NavigationContainer>
     </>
   );
 };

 // FTUEStack.js
 import React from 'react';
 import { View, Text, TouchableOpacity } from 'react-native';
 
-import { createStackNavigator } from 'react-navigation-stack';
+import { createStackNavigator } from '@react-navigation/stack';
 
 
 class Screen1 extends React.Component {
@@ -38,16 +38,15 @@ class Screen2 extends React.Component {
     }
 }
 
+const Stack = createStackNavigator();
 
-const FTUEStack = createStackNavigator({
-    Screen1: {
-      screen: Screen1,
-    },
-    Screen2: {
-        screen: Screen2,
-    },
-    initialRouteName: 'Screen1'
-  });
-
+function FTUEStack() {
+  return (
+    <Stack.Navigator initialRouteName="Screen1">
+      <Stack.Screen name="Screen1" component={Screen1} />
+      <Stack.Screen name="Screen2" component={Screen2} />
+    </Stack.Navigator>
+  );
+}
 
export {FTUEStack};

// package.json
   "dependencies": {
     "@react-native-community/masked-view": "^0.1.10",
+    "@react-navigation/native": "^5.9.3",
+    "@react-navigation/stack": "^5.14.3",
     "react": "16.13.1",
     "react-native": "0.63.4",
     "react-native-bundle-splitter": "^1.0.9",
     "react-native-gesture-handler": "1.5.0",
+    "react-native-reanimated": "^1.13.2",
     "react-native-safe-area-context": "^3.1.9",
-    "react-native-screens": "^2.17.1",
-    "react-navigation": "4.2.1",
-    "react-navigation-stack": "2.2.2",
-    "react-navigation-tabs": "2.10.1"
+    "react-native-screens": "^2.17.1"
   },

 //router.js

 import React from 'react';
 import { View, Text, TouchableOpacity } from 'react-native';
-import { createAppContainer, withNavigation } from 'react-navigation';
-import { createStackNavigator } from 'react-navigation-stack';
+import { createStackNavigator } from '@react-navigation/stack';
 
 import { register, preload } from 'react-native-bundle-splitter';
 
@@ -30,19 +29,21 @@ class HomeScreen extends React.Component {
   }
 }
 
-const HomeScreenWithNavigation = withNavigation(HomeScreen);
-
-const AppNavigator = createStackNavigator({
-  Home: {
-    screen: HomeScreenWithNavigation,
-  },
-  Ftue: {
-    screen: register({
-      require: () => require('./FTUEStack'),
-      name: "FTUE",
-      extract: 'FTUEStack'
-    })
-  }
+const FTUEStack = register({
+  require: () => require('./FTUEStack'),
+  name: "FTUE",
+  extract: "FTUEStack",
 });
 
-export default createAppContainer(AppNavigator);

+const Stack = createStackNavigator();
+
+function AppStack() {
+  return (
+    <Stack.Navigator>
+      <Stack.Screen name="Home" component={HomeScreen} />
+      <Stack.Screen name="Ftue" component={FTUEStack} />
+    </Stack.Navigator>
+  );
+}
+
+export default AppStack;

Thanks @kirillzyusko for the explanation !
I think moving to react navigation 5 will be a good option although we have a big project.