Closed ansh closed 1 year ago
There's a lot going on here so it's hard to say what the React Navigation-y workaround is. The long-term solution is to rewrite more parts of React Navigation so it doesn't need to render navigators in order to handle an event. This is exclusive to Expo Router since we always know how to handle routing ahead of time.
@EvanBacon I have simplified it and made a reproduction here: https://github.com/ansh/expo-router-auth-issue-repro
You can yarn
and yarn start
to check it out (Expo Go compatible reproduction). I am not using any auth library but instead just mocking some API endpoint for this reproduction.
As you can see, I have followed the expo-router docs on Authentication almost exactly, except I have the isLoading
if statement within my RootLayoutNav that renders a loading screen while data is fetching. This is a better user experience than showing the user the wrong screen. This was quite trivial to do with React Navigation but trying to figure out how to do this with expo-router has been quite difficult.
@EvanBacon Any updates on this? Happy to help if I can. Just point me to the correct direction.
This should be fixed on main
and will be included in the next version.
Thanks so much! Can you link to the PR it was fixed in? @marklawlor
There isn't a direct PR for this issue, just general refactoring. But we are now delay rendering the screens until the navigator is ready.
I'm using 1.7.2
and having then same issue, should this version have this solved? If I render <Slot />
conditionally after needed data is loaded always throws: Error: Got 'undefined' for the navigation state. You must pass a valid state object
.
For people who have this error, rule of thumb,
you MUST pass the navigation children down.
For example, I am using react native firebase, where in its doc, it suggests you to wait for the initialization to finish then render the UI, somethin like this:
if (!isInit) {
return <Text>Loading...</Text>
}
I put the above logic within a _layout.tsx
file, and got this error,
the reason is, this return prevents the children to be passed, instead, it renders this <Text>
component,
once I removed the above logic, no error at all.
dont worry, you can always find some layer to a logic like this :)
If you are still experiencing this issue with Expo Router v2 can you please provide a reproduction.
My workaround:
import { useNavigation } from "expo-router";
const navigation = useNavigation();
Using conditional with:
navigation.isReady()
``
still having this issue on v2
function RootLayoutNav() {
const colorScheme = useColorScheme();
const [firstLaunch, setFirstLaunch] = useState<boolean | null>(null);
const navigation = useNavigation();
const [isReady, setIsReady] = useState(false)
navigation.addListener('state', () => {
setIsReady(true)
});
useEffect(() => {
AsyncStorage.removeItem(APP_LAUNCHED_KEY);
AsyncStorage.getItem(APP_LAUNCHED_KEY).then((value) => {
console.log(value);
if (value === null) {
AsyncStorage.setItem(APP_LAUNCHED_KEY, "false");
setFirstLaunch(true);
} else {
setFirstLaunch(false);
}
});
}, []);
if (firstLaunch && isReady) {
return <Redirect href="onboarding"/>;
} else {
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{headerShown: false}}/>
<Stack.Screen name="modal" options={{presentation: 'modal'}}/>
</Stack>
</ThemeProvider>
);
}
}```
I'm also having this issue on V2.
I'm having this issue as well with V2, any conditional rendering causes the title error.
import useTheme from "../hooks/useTheme";
import { View } from "react-native";
import useAuth from "../hooks/useAuth";
import { Redirect } from "expo-router";
interface PublicLayoutProps {
children: ReactNode;
}
const PublicLayout = ({ children }: PublicLayoutProps) => {
const { theme } = useTheme();
const { currentUser } = useAuth();
if (currentUser && currentUser.isVerified) {
return <Redirect href="/private" />;
} else if (currentUser && !currentUser.isVerified) {
return <Redirect href="/unverified" />;
}
return (
<View style={{ backgroundColor: theme.background, height: "100%" }}>
{children}
</View>
);
};
export default PublicLayout;
I've been able to patch the first issue using useRootNavigation
, now there's the problem of actually redirecting before we have a slot available see:
New error:
ERROR 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.
function Root() {
const [onboarded, setOnboarded] = useMMKVBoolean("app.onboarded");
const navigation = useRootNavigation();
// const router = useRouter();
if (!onboarded && navigation?.isReady())
return <Redirect href="/onboarding/" />;
return (
<>
<Modals />
<Stack
screenOptions={{
headerShown: false,
headerTitleStyle: {
fontFamily: "Poppins_SemiBold",
fontSize: 18,
},
}}
>
<Stack.Screen name="(tabs)" />
<Stack.Screen
name="payment-modal"
options={{
headerShown: true,
presentation: "modal",
}}
/>
</Stack>
</>
);
}
I've been able to patch the first issue using
useRootNavigation
, now there's the problem of actually redirecting before we have a slot available see:New error:
ERROR 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.
If I'm not wrong, on expo-router we should manage the auth state via a context, at least, that's what's recommended:
I've been able to patch the first issue using
useRootNavigation
, now there's the problem of actually redirecting before we have a slot available see: New error:ERROR 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.
If I'm not wrong, on expo-router we should manage the auth state via a context, at least, that's what's recommended:
Yeah thats what makes sense and is also what I'm doing. The problem is the lack of documentation on edge cases of expo router. There's no explanation of these navigation errors, no explanation of the weird behaviour of unstable_settings
and no explanation of internal API's which are used in some of the examples.
Its documented for simple use, thats about it.
Any news on this?
For those wondering. This is the new Authentication reference docs for Expo Router. https://docs.expo.dev/router/reference/authentication/
They are quite good!
Anyone find a working solution for handling root-level redirect logic in Expo Router v2? The new docs mention the problem, but not how to solve the issue without nesting layouts.
Thanks @caioedut for a solution that worked in Expo Router v1...
My workaround:
import { useNavigation } from "expo-router";
const navigation = useNavigation();
Using conditional with:
navigation.isReady()
``
But that's been removed now in Expo Router v2.
--
edit: I was able to get this type of root layout router.push()
working without problems by replacing router = useRouter()
(using Solito's cross-platform router) to import { router } from "expo-router"
.
Summary
I am trying to set up some authentication using
expo-router
. So, I want to show a loading spinner while the auth data is fetching (ideally I wouldn't need this and could just keep the SplashScreen up but that's another story completely).Anyway, this is my root layout
For some reason, the code in the
RootLayoutNav
component that is shown below leads to the errorThe 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.
Not sure why this is happening. I assume it is because the root layout expects to render a Stack or a Slot or something, but not too sure if I'm doing something wrong or if there is a bug within expo-router
Minimal reproducible example
I added some code above but I can make a repo if necessary.