Closed andrew-levy closed 1 year ago
I solved it by adding
const [isNavigationReady, setNavigationReady] = useState(false);
const rootNavigation = useRootNavigation();
useEffect(() => {
const unsubscribe = rootNavigation?.addListener("state", (event) => {
setNavigationReady(true);
});
return function cleanup() {
if (unsubscribe) {
unsubscribe();
}
};
}, [rootNavigation]);
React.useEffect(() => {
if (!isNavigationReady) {
return;
}
const inAuthGroup = segments[0] === "(auth)";
if (!authInitialized) return;
if (
// If the user is not signed in and the initial segment is not anything in the auth group.
!user &&
!inAuthGroup
) {
// Redirect to the sign-in page.
router.push("/sign-in");
} else if (user && inAuthGroup) {
// Redirect away from the sign-in page.
router.push("/");
}
}, [user, segments, authInitialized, isNavigationReady]);
as detailed https://github.com/aaronksaunders/expo-router-v2-authflow-appwrite/blob/main/app/context/auth.tsx
Yeah, I'm having a similar issue:
Error: Attempted to navigate before mounting the Root Layout component. Ensure the Root Layout component is rendering a Slot, or other navigator on the first render.
app/_layout.tsx
import React, { useEffect, useState } from "react"; import { View } from "react-native"; import { Stack, SplashScreen, Slot } from "expo-router"; import { useFonts } from 'expo-font'; import FontAwesome from '@expo/vector-icons/FontAwesome'; import { SafeAreaProvider } from "react-native-safe-area-context"; import { Auth, FlashMessageWrapper } from "@components"; import { Provider } from "@store"; SplashScreen.preventAutoHideAsync(); export default function Layout() { const [loaded, error] = useFonts({ poppins: require('@assets/fonts/Poppins-Regular.ttf'), "poppins-light": require('@assets/fonts/Poppins-Light.ttf'), "poppins-medium": require('@assets/fonts/Poppins-Medium.ttf'), "poppins-bold": require('@assets/fonts/Poppins-Bold.ttf'), "poppins-semibold": require('@assets/fonts/Poppins-SemiBold.ttf'), ...FontAwesome.font, }); useEffect(() => { if (error) throw error; }, [error]); if (loaded) { SplashScreen.hideAsync(); } if (!loaded) { return null; } return ( <Provider> <FlashMessageWrapper /> <Auth> <SafeAreaProvider> <View style={{ flex: 1, }}> <Slot /> </View> </SafeAreaProvider> </Auth> </Provider> ); }
app/index.tsx
import { Redirect } from "expo-router"; export default function Index() { return <Redirect href={"/home"} /> }
But if I change the
return
of the Layout fromnull
to<Slot />
works but gives me a warning about the fonts used before they were loaded.from:
if (!loaded) { return null; }
to: (this is not a solution)
if (!loaded) { return <Slot />; }
After hours of trying, It worked! Thanks alot!
my solution is monitoring the navigationState.key then after this i apply the redirect
const navigationState = useRootNavigationState()
useEffect(() => { if (navigationState.key) { user.displayName ? router.replace('/index') : router.replace('/changeDisplayName') } }, [navigationState, user])
a note for anyone finding this issue. The problem mentions root, but the problem won't necesarily be there
I had to change this function
export default function Cliente () { const factura_context = useFacturaContext() if (factura_context?.state) { return ( ...render my stuff ) } else { router.push("/(main)/facturador"); return (<></>) } };
and replace else with this
} else { return (<Redirect href={"/(main)/facturador"}/>) }
I was having this issue again because I was returning a loading component in my context provider, I removed it and the error stopped showing up. I had already implemented the useRootNavigation
workaround in my protected route hook before:
export function MyProvider(props) {
const [isLoading, setIsLoading] = useState(true);
// This is where I redirect
useProtectedRoute();
// This is what I removed
if (isLoading) {
return <Text>Loading...</Text>
}
return (
<MyContext.Provider
value={{
...
}}
>
{props.children}
</MyContext.Provider>
);
}
create a hook:
import { useEffect, useState } from "react";
import { useRouter, useNavigationContainerRef } from "expo-router";
export default function useExtendedRouter() {
const [isNavigationReady, setIsNavigationReady] = useState(false);
const router = useRouter();
const navigationContainerRef = useNavigationContainerRef();
useEffect(() => {
const sub = navigationContainerRef.addListener("state", () => {
setIsNavigationReady(true);
});
return () => {
if (sub) {
sub();
}
};
}, [navigationContainerRef]);
return { isReady: isNavigationReady, ...router };
}
inside the component
const router = useExtendedRouter();
if(router.isReady){
<Redirect to="/home" />
}
useEffect(() => {
if (isAuthorized && router.isReady) {
router.replace('/home');
}
}, [ isAuthorized, router]);
import { FullScreenLoader } from "components/full-screen-loader"
import { useRootNavigationState, useRouter, useSegments } from "expo-router"
import * as secureStore from "expo-secure-store"
import { createContext, useContext, useEffect, useState } from "react"
interface AuthContextProps {
signOut: () => Promise<void>
}
const AuthContext = createContext({} as AuthContextProps)
export const useAuth = () => useContext(AuthContext)
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const router = useRouter()
const segments = useSegments()
const navigationState = useRootNavigationState()
const [isLoading, setIsLoading] = useState(true)
const signOut = async () => {
await secureStore.deleteItemAsync("JWTTOKEN")
router.replace("/(auth)/login")
}
useEffect(() => {
setIsLoading(false)
if (!navigationState?.key) return
const isAuthGroup = segments[0] === "(auth)"
const jwt = secureStore.getItem("JWTTOKEN")
if (!jwt && !isAuthGroup) {
router.replace("/(auth)/login")
} else if (jwt && isAuthGroup) {
router.replace("/(authenticated)/dashboard")
}
}, [segments])
return (
<AuthContext.Provider value={{ signOut }}>
{isLoading ? <FullScreenLoader /> : children}
</AuthContext.Provider>
)
}
import { useRootNavigationState } from 'expo-router'
export function useIsNavigationReady() {
const rootNavigationState = useRootNavigationState()
return rootNavigationState?.key != null
}
const isNavigationReady = useIsNavigationReady()
// Do not perform any routing operations as long as this variable is set to false.
dont understand why waiting for the listener to say "ready" is bad? … -- Aaron K. Saunders CEO Clearly Innovative Inc @. www.clearlyinnovative.com This email message and any attachment(s) are for the sole use of the intended recipient(s) and may contain proprietary and/or confidential information which may be privileged or otherwise protected from disclosure. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended recipient(s), please contact the sender by reply email and destroy the original message and any copies of the message as well as any attachment(s) to the original message. On Tue, Jul 18, 2023 at 8:49 AM Soucanye de Landevoisin < @.> wrote: Have hit the same thing upgrading from Expo-Router V1, and React Navigation before that. I guess the same useFonts example that many people were following. This is my current workaround to avoid navigating before the navigator is ready. It also avoids having to add a listener to the navigation state. Seems to work, until a neater solution is found. //index.tsx import { useRootNavigationState, Redirect } from 'expo-router'; export default function App() { const rootNavigationState = useRootNavigationState(); if (!rootNavigationState?.key) return null; return <Redirect href={'/test'} /> } Worked for me too. Just a thing, key doesn't exist on all states of rootNavigationState, so i use route for now. Still not a comfortable solution but it works — Reply to this email directly, view it on GitHub <#740 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEAFGMQUPAHGFKDN2E2QNTXQ2A4RANCNFSM6AAAAAA2ARJYTE . You are receiving this because you were mentioned.Message ID: @.***>
Because It should already be in Ready state... Thats why
It worked for me Instead a useEffect() inside index.tsx i use it inside _layout.tsx like this
//_layout.tsx
import { Stack, useRouter } from "expo-router"; import { useEffect,useState } from "react";
export default function RootLayout() {
const [logged,setLogged] = useState(true); const router = useRouter();
useEffect(()=>{ checkLogged(); },[]);
const checkLogged = ()=>{ if(logged) router.push("/group_1/inside"); }
return (
); }
For redirection purposes, have you ever tried using
const rootNavigationState = useRootNavigationState(); useEffect(() => { if(rootNavigationState?.routeNames?.includes('home')) router.replace('home'); },[rootNavigationState])
This work for me
For those trying to programmatically route with the router in expo router v3, using the the redirect component solved the issue for me.
In my app/(app)/_layout.tsx file
const AppLayout = () => {
const currentUser = contextObject?.currentUser;
if (isLoading) {
return <LoadingScreen />;
}
if(!currentUser) {
// BAD
// router.replace('/SignIn')
// Fixes the issue
return <Redirect href="/SignIn" />;
}
return <Stack />
}
Which package manager are you using? (Yarn is recommended)
yarn
Summary
I'm using the new docs to implement a basic authentication flow with redirects based on the user's auth status https://docs.expo.dev/router/reference/authentication/. In v1 and sdk 48, this works fine. But in v2 and sdk 49, I'm getting the following error when wrapping my app in an
AuthProvider
. It seems that navigating from within the root layout isn't allowed anymore.Example Repo: https://github.com/andrew-levy/expo-router-v2-repro
AuthProvider
wraps the app in the Root LayoutAuthProvider
callsuseProtectedRoute()
to check the auth status and redirect if necessaryExpo Router: v2 Expo SDK: 49
Minimal reproducible example
https://github.com/andrew-levy/expo-router-v2-repro