0

I have been using "@react-navigation/native": "^6.0.2" and the app worked well, after then I decided to update all dependencies and use "@react-navigation/native": "7.1.6" but then I keep getting an error message Call Stack RNCSafeAreaView (<anonymous>) ERROR Warning: Error: Couldn't get the navigation state. Is your component inside a navigator?

I really do not know what to do anymore, I have tried and searched for answers but couldn't find one

This is what my App.tsx looks like

import * as SplashScreen from "expo-splash-screen";
import * as SecureStore from "expo-secure-store";
import {useState, useEffect} from "react";
import {
    Inter_400Regular,
    Inter_500Medium,
    Inter_600SemiBold,
    Inter_700Bold,
    Inter_800ExtraBold,
} from "@expo-google-fonts/inter";
import {NavigationContainer} from "@react-navigation/native";

import Main from "./Main";
import {MainContext} from "./context/MainContext";
import {settingsType} from "./types";
import {
    SafeAreaProvider,
    SafeAreaView,
    useSafeAreaInsets,
} from "react-native-safe-area-context";
import {Appearance, StatusBar, View} from "react-native";
import {TC} from "./constants";

export default function App() {
    const [isFontReady, setIsFontReady] = useState<boolean>(false);
    const [isUserReady, setIsUserReady] = useState<boolean>(false);
    const [user, setUser] = useState(null);
    const [settings, setSettings] = useState<settingsType | undefined>(undefined);
    const [isLogin, setIsLogin] = useState<boolean>(false);
    const [colorScheme, setColorScheme] = useState(Appearance.getColorScheme());

    SplashScreen.preventAutoHideAsync();

    const [fontsLoaded] = Font.useFonts({
        regular: Inter_400Regular,
        medium: Inter_500Medium,
        "semi-bold": Inter_600SemiBold,
        bold: Inter_700Bold,
        "extra-bold": Inter_800ExtraBold,
    });

    useEffect(() => {
        async function prepare() {
            try {
                if (fontsLoaded && isUserReady) {
                    await SplashScreen.hideAsync();
                }

                if (fontsLoaded) {
                    setIsFontReady(true);
                }

                const user = await SecureStore.getItemAsync("user");
                const setting = await SecureStore.getItemAsync("settings");

                if (user) {
                    setUser(JSON.parse(user));
                    setIsLogin(true);
                }

                if (!setting) {
                    const newSetting: settingsType = {
                        appearance: "system",
                        passCode: null,
                        isBiometrics: false,
                    };
                    setSettings(newSetting);
                    await SecureStore.setItemAsync(
                        "settings",
                        JSON.stringify({
                            appearance: "system",
                            passCode: null,
                            isBiometrics: false,
                        })
                    );
                } else {
                    setSettings(JSON.parse(setting));
                }

                setIsUserReady(true);
            } catch (e) {
                console.warn(e);
            }
        }

        prepare();
    }, [fontsLoaded, isUserReady]);

    useEffect(() => {
        const colorListener = Appearance.addChangeListener(({colorScheme}) =>
            setColorScheme(colorScheme)
        );

        return () => {
            colorListener.remove();
        };
    }, []);

    if (!isFontReady || !isUserReady) return null;


    const theme =
        settings?.appearance === "system" && colorScheme
            ? colorScheme
            : settings?.appearance && settings.appearance !== "system"
                ? settings.appearance
                : "light";

    return (
        <MainContext
            settings={settings}
            setSettings={setSettings}
            colorScheme={colorScheme}
        >
            <StatusBar
                backgroundColor={TC[theme].bg1}
                barStyle={theme === "light" ? "dark-content" : "light-content"}
            />
            <SafeAreaProvider>
                <SafeAreaView
                    style={{
                        flex: 1,
                        backgroundColor: TC[theme].bg1,
                    }}
                >
                    <NavigationContainer>
                        <Main getLogin={isLogin} getUser={user} getSettings={settings}/>
                    </NavigationContainer>
                </SafeAreaView>
            </SafeAreaProvider>
        </MainContext>
    );
}

And after which I have the Main.tsx

import {useEffect, useRef, Fragment} from "react";
import {useNavigationState, useNavigation} from "@react-navigation/native";
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {BackHandler, ToastAndroid,} from "react-native";

import MainNavigation from "./navigation/MainNavigation";

import Profile from "./screens/Profile";
// import AddTransaction from "./screens/AddTransaction";
import Transaction from "./screens/Transaction";
import Settings from "./screens/Settings";
import ChangePassword from "./screens/ChangePassword";

import Login from "./screens/auth/Login";
import {
    SignUp,
    ForgotPassword,
    OnBoarding,
    Verification,
    AuthPin,
    EmailSent,
} from "@/screens/auth";

import Header from "./components/headers/Header";

import {useMainContext} from "./context/MainContext";
import {settingsType, userType} from "@/types";
import {TC} from "@/constants";
import {Airtime, Cable, Data, Electricity} from "./screens/bills";

const Main = ({
                  getUser,
                  getLogin,
                  getSettings,
              }: {
    getUser: userType | null;
    getSettings: settingsType | undefined;
    getLogin: boolean;
}) => {
    const Stack = createNativeStackNavigator();
    const {
        isLogin,
        setUser,
        setIsLogin,
        isLocked,
        // user,
        setSettings,
        theme,
        settings,
    } = useMainContext();

    const doublePressInterval = 1000;
    const lastBackPressed = useRef(0);
    const navigation = useNavigation();
    const navigationState = useNavigationState((state) => state);

    useEffect(() => {
        const backHandler = BackHandler.addEventListener(
            "hardwareBackPress",
            () => {
                const currentTime = Date.now();

                const state = navigation.getState();

                // Check if the current screen is the root screen
                const isRootScreen = state?.routes.length === 1 && state.index === 0;

                if (isRootScreen) {
                    // Check if the time interval between presses is less than the double press interval
                    if (currentTime - lastBackPressed.current < doublePressInterval) {
                        // Exit the application
                        BackHandler.exitApp();
                        return true;
                    }

                    // Show a toast indicating double press is required to exit
                    ToastAndroid.show("Press back again to exit", ToastAndroid.SHORT);

                    // Update the lastBackPressed time
                    lastBackPressed.current = currentTime;

                    return true;
                }

                return false;
            }
        );

        return () => backHandler.remove();
    }, [navigationState]);

    useEffect(() => {
        setUser(getUser);
        setIsLogin(getLogin);
        setSettings(getSettings);
    }, [getUser]);

    return (
        <Stack.Navigator>
            {isLogin ? (
                <Fragment>
                    {!settings?.passCode ? (
                        <Stack.Screen
                            name="CreatePin"
                            component={AuthPin}
                            initialParams={{mode: "create-pin"}}
                            options={{headerShown: false}}
                        />
                    ) : isLocked ? (
                        <Stack.Screen
                            name="Locked"
                            component={AuthPin}
                            initialParams={{mode: "locked"}}
                            options={{headerShown: false}}
                        />
                    ) : (
                        <Fragment>
                            <Stack.Screen
                                name="main-navigation"
                                component={MainNavigation}
                                options={{headerShown: false}}
                            />
                            <Stack.Screen
                                name="profile"
                                component={Profile}
                                options={{headerShown: false}}
                            />
                            {/* <Stack.Screen
                name="add-transaction"
                component={AddTransaction}
                options={{ headerShown: false }}
              /> */}
                            <Stack.Screen
                                name="transaction"
                                component={Transaction}
                                options={{headerShown: false}}
                            />
                            <Stack.Screen
                                name="settings"
                                component={Settings}
                                options={{header: () => <Header text="Settings"/>}}
                            />
                            <Stack.Screen
                                name="password"
                                component={ChangePassword}
                                options={{
                                    header: () => <Header text="Change Password"/>,
                                }}
                            />
                            <Stack.Screen
                                name="ChangePin"
                                component={AuthPin}
                                options={{headerShown: false}}
                                initialParams={{mode: "change-pin"}}
                            />
                            <Stack.Screen
                                name="Airtime"
                                component={Airtime}
                                options={{
                                    header: () => (
                                        <Header color={TC[theme].text1} bg={TC[theme].bg1}/>
                                    ),
                                }}
                            />
                            <Stack.Screen
                                name="Data"
                                component={Data}
                                options={{
                                    header: () => (
                                        <Header color={TC[theme].text1} bg={TC[theme].bg1}/>
                                    ),
                                }}
                            />
                            <Stack.Screen
                                name="Cable"
                                component={Cable}
                                options={{
                                    header: () => (
                                        <Header color={TC[theme].text1} bg={TC[theme].bg1}/>
                                    ),
                                }}
                            />
                            <Stack.Screen
                                name="Electricity"
                                component={Electricity}
                                options={{
                                    header: () => (
                                        <Header color={TC[theme].text1} bg={TC[theme].bg1}/>
                                    ),
                                }}
                            />
                        </Fragment>
                    )}
                </Fragment>
            ) : (
                <Fragment>
                    <Stack.Screen
                        name="on-boarding"
                        component={OnBoarding}
                        options={{headerShown: false}}
                    />
                    <Stack.Screen
                        name="login"
                        component={Login}
                        options={{
                            header: () => (
                                <Header
                                    text="Login"
                                    color={TC[theme].text1}
                                    bg={TC[theme].bg1}
                                />
                            ),
                        }}
                    />
                    <Stack.Screen
                        name="signup"
                        component={SignUp}
                        options={{
                            header: () => (
                                <Header
                                    text="Sign Up"
                                    color={TC[theme].text1}
                                    bg={TC[theme].bg1}
                                />
                            ),
                        }}
                    />
                    <Stack.Screen
                        name="verification"
                        component={Verification}
                        options={{
                            header: () => (
                                <Header
                                    text="Verification"
                                    color={TC[theme].text1}
                                    bg={TC[theme].bg1}
                                />
                            ),
                        }}
                    />
                    <Stack.Screen
                        name="forgot-password"
                        component={ForgotPassword}
                        options={{
                            header: () => (
                                <Header
                                    text="Forgot Password"
                                    color={TC[theme].text1}
                                    bg={TC[theme].bg1}
                                />
                            ),
                        }}
                    />
                    <Stack.Screen
                        name="email-sent"
                        component={EmailSent}
                        options={{headerShown: false}}
                    />
                </Fragment>
            )}
        </Stack.Navigator>
    );
};

export default Main;

And then the MainNavigation.tsx

import {
    BottomTabBarProps,
    createBottomTabNavigator,
} from "@react-navigation/bottom-tabs";
import {MaterialCommunityIcons} from "@expo/vector-icons";
import {StyleSheet, TouchableOpacity, View} from "react-native";

import {TABS} from "@/constants";
import {icons, ScreenNames} from "@/types";
import {COLORS, TC} from "@/constants";
import {useMainContext} from "@/context/MainContext";

import Home from "@/screens/main-navigation/Home";
import Transactions from "@/screens/main-navigation/Transactions";
import Fund from "@/screens/main-navigation/Fund";
import Profile from "@/screens/Profile";
import Settings from "@/screens/Settings";
import PopAction from "@/components/PopAction";
import {Fragment} from "react";
import {getFocusedRouteNameFromRoute, NavigationContainer} from "@react-navigation/native";

const Tab = createBottomTabNavigator();

export default function MainNavigation({route}: { route: any }) {
    const {theme, handleLogout, isLogoutVisible, setIsLogoutVisible} =
        useMainContext();

    const focusedTab = getFocusedRouteNameFromRoute(route);

    const tabBar = ({state, descriptors, navigation}: BottomTabBarProps) => (
        <View style={[styles.tabBar, {backgroundColor: TC[theme].tabBar}]}>
            {state.routes.map((route, index) => {
                const {options} = descriptors[route.key];
                const label =
                    options.tabBarLabel !== undefined
                        ? options.tabBarLabel
                        : options.title !== undefined
                            ? options.title
                            : route.name;
                const focused = state.index === index;
                const Icon =
                    options.tabBarIcon &&
                    options.tabBarIcon({focused, color: COLORS.blue100, size: 35});

                const screenName = route.name as ScreenNames;

                return label === "fund" ? (
                    <View
                        key={route.key}
                        style={[styles.action, {backgroundColor: TC[theme].action}]}
                    >
                        <TouchableOpacity
                            onPress={() => navigation.navigate(screenName)}
                            style={[
                                styles.actionBtn,
                                {backgroundColor: focused ? COLORS.light100 : COLORS.blue100},
                            ]}
                        >
                            {Icon}
                        </TouchableOpacity>
                    </View>
                ) : (
                    <TouchableOpacity
                        key={route.key}
                        onPress={() => navigation.navigate(screenName)}
                        style={{
                            alignItems: "center",
                            justifyContent: "center",
                            height: 50,
                        }}
                    >
                        {Icon}
                    </TouchableOpacity>
                );
            })}
        </View>
    );

    return (
        <Fragment>
            <Tab.Navigator
                initialRouteName="Home"
                screenOptions={{
                    tabBarHideOnKeyboard: true,
                }}
                tabBar={tabBar}
            >
                {TABS(Home, Transactions, Fund, Profile, Settings).map((tab, index) => (
                    <Tab.Screen
                        name={tab.name}
                        component={tab.component}
                        options={{
                            tabBarIcon: ({focused}) => {
                                return (
                                    <MaterialCommunityIcons
                                        name={
                                            focused ? (tab.focused as icons) : (tab.idle as icons)
                                        }
                                        color={focused ? COLORS.focused : TC[theme].idle}
                                        size={tab.name === "fund" ? 45 : 38}
                                    />
                                );
                            },
                            headerShown: false,
                        }}
                        key={index}
                    />
                ))}
            </Tab.Navigator>
            {focusedTab === "profile" && (
                <PopAction
                    visible={isLogoutVisible}
                    onCancel={() => setIsLogoutVisible(false)}
                    onComplete={handleLogout}
                    headText="Logout?"
                    bodyText="Are you sure you want to logout"
                />
            )}
        </Fragment>
    );
}

const styles = StyleSheet.create({
    tabBar: {
        flexDirection: "row",
        alignItems: "flex-end",
        justifyContent: "space-between",
        paddingHorizontal: 20,
        paddingBottom: 10,
        position: "absolute",
        right: 0,
        left: 0,
        bottom: 0,
        backgroundColor: COLORS.dark25,
        borderTopRightRadius: 20,
        borderTopLeftRadius: 20,
    },
    action: {
        backgroundColor: COLORS.light100,
        height: 50,
        width: 100,
        marginBottom: 20,
        alignItems: "center",
        justifyContent: "center",
        borderBottomLeftRadius: 50,
        borderBottomRightRadius: 50,
    },
    actionBtn: {
        alignItems: "center",
        justifyContent: "center",
        height: 70,
        width: 70,
        backgroundColor: COLORS.blue100,
        borderRadius: 100,
        marginTop: -45,
    },
});

I really do not know where the problem is coming from because it was working in the previous version

Here is also my installed packages

{
  "name": "salome_trakrr",
  "main": "expo/AppEntry.js",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "lint": "expo lint"
  },
  "dependencies": {
    "@expo-google-fonts/inter": "^0.4.1",
    "@expo/vector-icons": "^14.1.0",
    "@react-native-community/blur": "^4.4.1",
    "@react-navigation/bottom-tabs": "^7.3.10",
    "@react-navigation/native": "^7.1.6",
    "@react-navigation/native-stack": "^7.3.24",
    "axios": "^1.11.0",
    "dayjs": "^1.11.13",
    "expo": "~53.0.20",
    "expo-blur": "~14.1.5",
    "expo-constants": "~17.1.7",
    "expo-font": "~13.3.2",
    "expo-haptics": "~14.1.4",
    "expo-image": "~2.4.0",
    "expo-linking": "~7.1.7",
    "expo-local-authentication": "^16.0.5",
    "expo-secure-store": "^14.2.3",
    "expo-splash-screen": "~0.30.10",
    "expo-status-bar": "~2.2.3",
    "expo-symbols": "~0.4.5",
    "expo-system-ui": "~5.0.10",
    "expo-web-browser": "~14.2.0",
    "react": "19.0.0",
    "react-dom": "19.0.0",
    "react-native": "0.79.5",
    "react-native-gesture-handler": "~2.24.0",
    "react-native-reanimated": "~3.17.4",
    "react-native-root-toast": "^4.0.1",
    "react-native-safe-area-context": "5.4.0",
    "react-native-screens": "~4.11.1",
    "react-native-swiper": "^1.6.0",
    "react-native-webview": "13.13.5"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@types/react": "~19.0.10",
    "eslint": "^9.25.0",
    "eslint-config-expo": "~9.2.0",
    "typescript": "~5.8.3"
  },
  "private": true
}

1 Answer 1

0

I later found out where the problem was

It lied on this particular line

const navigation = useNavigation();
const navigationState = useNavigationState((state) => state);

    useEffect(() => {
        const backHandler = BackHandler.addEventListener(
            "hardwareBackPress",
            () => {
                const currentTime = Date.now();

                const state = navigation.getState();

                // Check if the current screen is the root screen
                const isRootScreen = state?.routes.length === 1 && state.index === 0;

                if (isRootScreen) {
                    // Check if the time interval between presses is less than the double press interval
                    if (currentTime - lastBackPressed.current < doublePressInterval) {
                        // Exit the application
                        BackHandler.exitApp();
                        return true;
                    }

                    // Show a toast indicating double press is required to exit
                    ToastAndroid.show("Press back again to exit", ToastAndroid.SHORT);

                    // Update the lastBackPressed time
                    lastBackPressed.current = currentTime;

                    return true;
                }

                return false;
            }
        );

        return () => backHandler.remove();
    }, [navigationState]);

In the newer versions of @react-navigation/native I couldn't use useNavigation or useNavigationState outside a screen so I reformed the code and used a ref

I created a NavigationRef.ts and inside I created this ref

import {createNavigationContainerRef} from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();

Then I reformed the App.tsx

...
<MainContext
            settings={settings}
            setSettings={setSettings}
            colorScheme={colorScheme}
        >
            <StatusBar
                backgroundColor={TC[theme].bg1}
                barStyle={theme === "light" ? "dark-content" : "light-content"}
            />
            <SafeAreaProvider>
                <SafeAreaView
                    style={{
                        flex: 1,
                        backgroundColor: TC[theme].bg1,
                    }}
                >
                    <NavigationContainer ref={navigationRef}>
                        <Main getLogin={isLogin} getUser={user} getSettings={settings}/>
                    </NavigationContainer>

                </SafeAreaView>
            </SafeAreaProvider>


        </MainContext>

Then inside the Main.tsx

...
useEffect(() => {
        const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
        const currentTime = Date.now();

            const state = navigationRef.getCurrentRoute(); // Use ref instead of hook
            const navState = navigationRef.getRootState();

            const isRootScreen = navState?.routes.length === 1 && navState.index === 0;

            if (isRootScreen) {
                if (currentTime - lastBackPressed.current < doublePressInterval) {
                    BackHandler.exitApp();
                    return true;
                }

                ToastAndroid.show("Press back again to exit", ToastAndroid.SHORT);
                lastBackPressed.current = currentTime;
                return true;
            }

            return false;
        });

        return () => backHandler.remove();
    }, []);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.