expo / expo

An open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.
https://docs.expo.dev
MIT License
32.02k stars 5.07k forks source link

After upgrading 46 to 47 and enabled hermes sometimes my app screen is not showing #20309

Closed shop-fluencer closed 1 year ago

shop-fluencer commented 1 year ago

Summary

€: This happens when I import useTranslation from react-i18next

import { useTranslation } from 'react-i18next'
const { t } = useTranslation();

Hello,

After upgrading 46 to 47 and enabled hermes sometimes my Home screen is not showing. Sometimes its showing sometimes not. I dont understand why at the prev update I never had this problem. Maybe its because of enabled hermes ?

I have bottom TabBar. So the first screen is empty and not showing while other screens are working fine. this happened after upgrading. In the prev sdk it works perfect. Look at the picture my screen is empty.

WhatsApp Image 2022-12-03 at 13 47 00

€: error sometimes:

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.
 ERROR  Error: Requiring unknown module "undefined". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`., js engine: hermes
 ERROR  Invariant Violation: Failed to call into JavaScript module method AppRegistry.runApplication(). Module has not been registered as callable. Registered callable JavaScript modules (n = 11): Systrace, JSTimers, HeapCapture, SamplingProfiler, RCTLog, RCTDeviceEventEmitter, RCTNativeAppEventEmitter, GlobalPerformanceLogger, JSDevSupportModule, HMRClient, RCTEventEmitter.
        A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes       
 ERROR  Invariant Violation: Failed to call into JavaScript module method AppRegistry.runApplication(). Module has not been registered as callable. Registered callable JavaScript modules (n = 11): Systrace, JSTimers, HeapCapture, SamplingProfiler, RCTLog, RCTDeviceEventEmitter, RCTNativeAppEventEmitter, GlobalPerformanceLogger, JSDevSupportModule, HMRClient, RCTEventEmitter.
        A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes  

What platform(s) does this occur on?

Android, iOS

Environment

expo-env-info 1.0.5 environment info: System: OS: Windows 10 10.0.19044 Binaries: Node: 16.13.2 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.11 - ~\AppData\Roaming\npm\yarn.CMD npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD IDEs: Android Studio: Version 2020.3.0.0 AI-203.7717.56.2031.7583922 npmPackages: expo: ^47.0.0 => 47.0.8 react: 18.1.0 => 18.1.0 react-dom: 18.1.0 => 18.1.0 react-native: 0.70.5 => 0.70.5 react-native-web: ~0.18.7 => 0.18.8 Expo Workflow: managed

Minimal reproducible example

Nav

App.js

enableScreens(true);

// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();

export default function App() {
  const [ready, setReady] = useState<boolean>(false);

  useEffect(() => {
    (async () => {
      await Font.loadAsync({
        Fredoka_medium: require('./assets/fonts/Catamaran-SemiBold.ttf'),
        Fredoka_light: require('./assets/fonts/Catamaran-Regular.ttf'),
        Fredoka_regular: require('./assets/fonts/Catamaran-Medium.ttf'),
      });
    //  const userToken = await SecureStore.getItemAsync('UserSecureToken');
    const userToken = 2121;
      if(userToken) {
        store.dispatch(setAuth());
        setReady(true);
      } else {
       // await SecureStore.setItemAsync('UserSecureToken', 'test_token');
        setReady(true);
      }
    })()
  }, []);

  const onLayoutRootView = useCallback(async () => {
    if (ready) {
      // This tells the splash screen to hide immediately! If we call this after
      // `setAppIsReady`, then we may see a blank screen while the app is
      // loading its initial state and rendering its first pixels. So instead,
      // we hide the splash screen once we know the root view has already
      // performed layout.
      await SplashScreen.hideAsync();
    }
  }, [ready]);

  if(!ready) {
    return null;
  }

  return (
  <Suspense fallback={<Text>Loading...</Text>}>
    <Provider store={store}>
     <SafeAreaProvider>
      <SafeAreaView style={s.rootView}>
      <GestureHandlerRootView style={s.rootView}>
        <NavigationContainer onReady={onLayoutRootView}>
            <Host>
              <NativeStackWrapper />
            </Host>
            <Toast position='bottom' config={ToastConfig} />
        </NavigationContainer>
        </GestureHandlerRootView>
        </SafeAreaView>
      </SafeAreaProvider>
    </Provider>
  </Suspense>
  );
}
const { width } = Dimensions.get('window');

const NativeStackNavigator = createNativeStackNavigator<RootStackParams>();

const BottomBar = createBottomTabNavigator();

const tabBarHeight = 55;
const middleIconSize = 50;
const midRadius = 25;
const midBoundary = 60;

function MyTabBar({ state, navigation }: IRenderTabBar) {
  const path = [
    'M0 0',
    `H${width / 2.1 - midBoundary / 2.2}`,
    `A 10 10 0 0 0 ${width / 1.95 + midBoundary / 2} 0`,
    `H${width}`,
    `V${tabBarHeight}`,
    'H0',
    `z`,
  ].join(',');

  const linePath = [
    'M0 0',
    `H${width / 2 - midBoundary / 2}`,
    `A 10 10 0 0 0 ${width / 2 + midBoundary / 2} 0`,
    `H${width}`,
  ].join(',');

  const showIcon = useCallback((index, focused) => {
    if(index === 0) {
      return <Ionicons name="ios-home-outline" size={24} color={focused ? '#111' : 'gray'} />
    }

    if(index === 1) {
      return <Fontisto name="world-o" size={24} color={focused ? '#111' : 'gray'} />
    }

    if(index === 3) {
      return <FontAwesome name="tv" size={24} color={focused ? '#111' : 'gray'} />
    }

    if(index === 4) {
      return  <Feather name="user" size={24} color={focused ? '#111' : 'gray'} />
    }
  }, [navigation]);

  const mRef = useRef<BottomSheetModal>(null);

  const handlePressCreateNavigation = () => mRef.current?.close();
  return (
    <View
      style={s.svgContainer}>
      <Svg
        viewBox={`0 0 ${width} ${tabBarHeight}`}
        height={tabBarHeight}
        width={width}>
        <Path d={path} fill="#fff" />
        <Path d={linePath} fill="transparent" strokeWidth={0} stroke="#fff" />
      </Svg>
      <Button
        onPress={() => mRef.current?.present()}
        style={s.roundedBtn}
      >
        <Feather name="plus" size={24} color='#fff' />
      </Button>
      <View style={s.nav}>
        {state.routes.map((route: { key: string; name: string; params: object | undefined }, index: number) => {

          const isFocused = state.index === index;

          const onPress = () => {
            const event = navigation.emit({
              type: 'tabPress',
              target: route.key,
              canPreventDefault: true,
            });

            if (!isFocused && !event.defaultPrevented) {
              // The `merge: true` option makes sure that the params inside the tab screen are preserved
              navigation.navigate({ name: route.name, merge: true });
            }
          };

          if(index !== 2) {
            return (
              <Button
                key={index}
                onPress={onPress}
                style={[s.navBtns, index === 1 && s.margin]}>
                {
                  showIcon(index, isFocused)
                }
              </Button>
            );
          }
        })}
      </View>
      <Portal>
        <BottomSheetModalProvider>
        <ModalCreate
          ref={mRef}
          onPress={handlePressCreateNavigation}
        />
        </BottomSheetModalProvider>
      </Portal>
    </View>
  );
}

const ViewTest = () => {
  return <View />;
}

const TabBar = () => {
    return (
    <BottomBar.Navigator
      tabBar={(props) => <MyTabBar {...props} />}
      screenOptions={{headerShown: false, tabBarHideOnKeyboard: Platform.OS !== 'ios'}}
    >
      <BottomBar.Screen
        name="Home"
        component={Home}
      />
      <BottomBar.Screen
        name="Discover"
        component={Discover}
      />
      <BottomBar.Screen
        name="Plus"
        component={Chat}
      />
      <BottomBar.Screen
        name={'ShoppingCart'}
        component={ViewTest}
      />
      <BottomBar.Screen
        name="Profile"
        component={Profile}
      />
    </BottomBar.Navigator>
    )
};

export const NativeStackWrapper = () => {
  const AuthState = useSelector((state: RootState) => state.Auth.isAuth);
  return (
    <NativeStackNavigator.Navigator screenOptions={{fullScreenGestureEnabled: true}}>
      <NativeStackNavigator.Screen name="BottomTab" component={TabBar} options={({navigation, route}) => ({
        header: () => {
          const routeName = getFocusedRouteNameFromRoute(route);
          if(routeName !== 'Profile') {
            return (
              <HeaderMain />
            )
          } else {
            return (
              <HeaderNone />
            )
          }
        }
      })} />
      { !AuthState && (
        <>
          <NativeStackNavigator.Screen name="Auth" component={Auth} options={{headerShown: false}} />
          <NativeStackNavigator.Screen name="SocialLoginRegister" component={SocialLoginRegister} options={{headerShown: false}} />
          <NativeStackNavigator.Screen name="ForgotPassword" component={ForgotPassword} options={{headerShown: false}} />
        </>
        )
      }
  )
}

Home:

const { width } = Dimensions.get('window');

const HALF_WIDTH = width / 2;

const HomeRLV = ({ onNavigateToSearchScreen, header }: IHomeRLV) => {
  const { t } = useTranslation();
  const navigation = useNavigation<NativeStackNavigationProp<RootStackParams>>();
  const wishlist = useSelector((state: RootState) => state.WishList.wishlist);
  const dispatch = useDispatch();

  const user_id = useSelector((state: RootState) => state.Auth.user.user_id);

  const [currentProductID, setCurrentProductID] = useState<string | null>(null);

  const { data, isFetching } = useHomeListQuery();
  const { data: data2, isFetching: is2 } = useTestListQuery();

  const [addWishlist] = useUpdateHomelistMutation();

  const handleAddToWishList = async ({ product_id, product, user_id }: { product_id: number; product: IProduct; user_id?: number; }) => {
    try {
      const data = await addWishlist({product_id, product, user_id});
      console.log(data);
    } catch(e) {
      console.error(e);
    }
  };

  return (
    <View style={s.container}>
      <SearchInputWithoutInputtouch onPress={onNavigateToSearchScreen} />
      <Text>Hello</Text>
    </View>
  )
}
mariomurrent-softwaresolutions commented 1 year ago

This looks like as the error says: Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.

Did you go through all the changelog in the upgrade guide?

shop-fluencer commented 1 year ago

This looks like as the error says: Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.

Did you go through all the changelog in the upgrade guide?

Its not the require cycling, I had never this issue except I enabled hermes and this happens now when I use this:

import { useTranslation } from 'react-i18next'
const { t } = useTranslation();

if I remove it then it works, maybe a bug ?

mariomurrent-softwaresolutions commented 1 year ago

You can easy verify this if you create a new project and just import the translation library.

It's hard to say without knowing all of your code

SohelIslamImran commented 1 year ago

After upgrading to SDK47, eas and Hermes, my splash image does not cover the top.

iegik commented 1 year ago

@shop-fluencer Refactor little-bit:

shop-fluencer commented 1 year ago

@shop-fluencer Refactor little-bit:

  • path and linePath can be used outside MyTabBar (decelerating objects inside component is a bed idea)
  • Move out screenOptions value outside TabBar and NativeStackWrapper
  • useUpdateHomelistMutation not declared
  • handleAddToWishList not used but decelerated on every render.
  • Move out NativeStackNavigator.Screen's options outside NativeStackWrapper
  • key={index} - wrong

Thanks for the Refactoring, You mean screen options outside of my component like this ?

This

const BuyActionOptions = ({navigation, route}) => ({
  header: () => <HeaderCreateShop title='Kaufabschließung' />
});

<NativeStackNavigator.Screen name="BuyAction" component={BuyAction} options={BuyActionOptions} />

instead of this

<NativeStackNavigator.Screen name="BuyAction" component={BuyAction} options={({navigation, route}) => ({
  header: () => <HeaderCreateShop title='Kaufabschließung' />
})} />

and I some function/mutations are not declared because it is decalred but I removed all my components in View to check where the error comes, it comes when I use const { t } = useTranslation().

And maybe handleAddToWishlist is used, should I use it on callback ? I mean its a function but it is not running on every rerender its only running when I call the function

iegik commented 1 year ago

@shop-fluencer

@shop-fluencer: Thanks for the Refactoring, You mean screen options outside of my component like this ? Must read: https://beta.reactjs.org/learn/javascript-in-jsx-with-curly-braces

https://beta.reactjs.org/learn/your-first-component#nesting-and-organizing-components

// 🔴 Never define a component inside another component!

@shop-fluencer: And maybe handleAddToWishlist is used, should I use it on callback ? I mean its a function but it is not running on every rerender its only running when I call the function https://beta.reactjs.org/apis/react/useCallback#usecallback

You should wrap functions into useCallback because component may re-render any time, when paren component renders and props changed and so on.. But should you use useCallback hook, when handleAddToWishlist function content never changes? Answer is no but only when that function is declared "outside" the declaration of component itself.

const Foo = () => {
  const bar = async () => {} // "bar" will be declared on every re-render
  return <Tar moo={bar} />
}

then:

const Foo = () => {
  const bar = useCallback(async () => {}),[]) // useCallback will be called and memory will be allocated for "[]" on every re-render
  return <Tar moo={bar} />
}

finally:

const bar = async () => {} // "bar" will be declared only once on import
const Foo = () => {
  return <Tar moo={bar} />
}

Useful pages:

iegik commented 1 year ago

Over all, I think, that part of code may cause re-render:

const { width } = Dimensions.get('window');
...
const path = [
    'M0 0',
    `H${width / 2.1 - midBoundary / 2.2}`,
    `A 10 10 0 0 0 ${width / 1.95 + midBoundary / 2} 0`,
    `H${width}`,
    `V${tabBarHeight}`,
    'H0',
    `z`,
expo-bot commented 1 year ago

Hi there! It looks like your issue requires a minimal reproducible example, but it is invalid or absent. Please prepare such an example and share it in a new issue.

The best way to get attention to your issue is to provide a clean and easy way for a developer to reproduce the issue on their own machine. Please do not provide your entire project, or a project with more code than is necessary to reproduce the issue.

A side benefit of going through the process of narrowing down the minimal amount of code needed to reproduce the issue is that you may get lucky and discover that the bug is due to a mistake in your application code that you can quickly fix on your own.

Resources