将Login页设为UserProvider fallback时跳转Signup页遇导航对象未初始化错误
问题:Realm同步应用中登录页无法跳转到注册页
已实现Realm Device Sync,采用邮箱/密码认证方式登录。按照模板步骤将Navigation Container置于Realm的AppProvider和UserProvider组件顶层,未登录时会显示Login页,但无法从Login页跳转到Signup页。尝试过将Navigation Container移到Realm Provider和App Provider上方,问题仍未解决。
相关代码
App.tsx
import "react-native-gesture-handler"; import { NavigationContainer } from "@react-navigation/native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import NativeStack from "./src/routes/NativeStack"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { PaperProvider } from "react-native-paper"; import { AppProvider, UserProvider } from "@realm/react"; import { appId, baseUrl } from "./atlasConfig.json"; import Login from "./src/screens/authScreen/Login"; import realmContext from "./src/data/dbContext"; import Loading from "./src/screens/loadingScreen/Loading"; export default function App() { // Getting Realm Provider const { RealmProvider } = realmContext; return ( <PaperProvider> <SafeAreaProvider> <GestureHandlerRootView style={{ flex: 1 }}> <NavigationContainer> <AppProvider id={appId} baseUrl={baseUrl}> <UserProvider fallback={Login}> <RealmProvider sync={{ flexible: true, onError: (_, error) => { console.log(error); }, initialSubscriptions: { update(subs, realm) { subs.add(realm.objects("Company")), subs.add(realm.objects("Purchase")), subs.add(realm.objects("Payment")); }, }, }} fallback={Loading} > <NativeStack /> </RealmProvider> </UserProvider> </AppProvider> </NavigationContainer> </GestureHandlerRootView> </SafeAreaProvider> </PaperProvider> ); }
Login.tsx
import Realm from "realm"; import { Keyboard, NativeSyntheticEvent, StyleSheet, TextInputChangeEventData, TouchableWithoutFeedback, View, } from "react-native"; import React, { useCallback, useState } from "react"; import { StackActions, useNavigation } from "@react-navigation/native"; import { SafeAreaView } from "react-native-safe-area-context"; import { Button, Text, TextInput } from "react-native-paper"; import { useApp } from "@realm/react"; const Login = () => { // Realm App Context const app = useApp(); // Navigation const nav = useNavigation(); // Form input variables const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); // Toggle Password Visibility const [passwordVisibility, setPasswordVisibility] = useState(true); // Action Handlers const formClear = () => { setEmail(""); setPassword(""); setPasswordVisibility(true); }; const registerHandler = () => { nav.dispatch(StackActions.replace("Register")); formClear(); }; const loginHandler = useCallback(async () => { const creds = Realm.Credentials.emailPassword({ email, password }); await app.logIn(creds); formClear(); }, [app, email, password]); return ( <TouchableWithoutFeedback onPress={Keyboard.dismiss} style={styles.containerWrapper} > <SafeAreaView style={styles.containerWrapper}> <View style={styles.container}> <View style={styles.headerContainer}> <Text variant="titleLarge" style={styles.headerLabel}> Register </Text> </View> <View> <TextInput label="Email" placeholder="Email" value={email} autoCapitalize="none" onChange={(e: NativeSyntheticEvent<TextInputChangeEventData>) => { setEmail(e.nativeEvent.text); }} /> <TextInput label="Password" placeholder="Password" value={password} autoCapitalize="none" secureTextEntry={passwordVisibility} onChange={(e: NativeSyntheticEvent<TextInputChangeEventData>) => { setPassword(e.nativeEvent.text); }} right={ <TextInput.Icon icon="eye" onPress={() => { setPasswordVisibility((previousState) => !previousState); }} /> } /> </View> <View style={styles.buttonContainer}> <Button mode="contained" onPress={loginHandler}> Login </Button> <Button mode="outlined" onPress={registerHandler}> Don't have an Account? Register </Button> </View> </View> </SafeAreaView> </TouchableWithoutFeedback> ); }; export default Login; const styles = StyleSheet.create({ containerWrapper: { flex: 1, }, container: { flex: 1, }, headerContainer: {}, headerLabel: {}, inputContainer: { gap: 20, }, buttonContainer: {}, button: {}, });
Signup.tsx
import Realm from "realm"; import { Alert, Keyboard, NativeSyntheticEvent, StyleSheet, TextInputChangeEventData, TouchableWithoutFeedback, View, } from "react-native"; import React, { useCallback, useState } from "react"; import { SafeAreaView } from "react-native-safe-area-context"; import { Button, Text, TextInput } from "react-native-paper"; import { StackActions, useNavigation } from "@react-navigation/native"; import { useApp } from "@realm/react"; const Register = () => { // App Context for Realm const app = useApp(); // Navigation const nav = useNavigation(); // Form input variables const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); // Toggle Password Visibility const [passwordVisibility, setPasswordVisibility] = useState(true); const [confirmPasswordVisibility, setConfirmPasswordVisibility] = useState(true); // Action Handlers const formClear = () => { setEmail(""); setPassword(""); setConfirmPassword(""); setPasswordVisibility(true); setConfirmPasswordVisibility(true); }; const signIn = useCallback(async () => { const creds = Realm.Credentials.emailPassword({ email, password }); await app.logIn(creds); }, [app, email, password]); const registerHandler = useCallback(async () => { try { await app.emailPasswordAuth.registerUser({ email, password }); await signIn(); formClear(); } catch (error: any) { Alert.alert(`Failed to Register: ${error?.message}`); } }, [signIn, app, email, password]); const loginHandler = () => { nav.dispatch(StackActions.replace("Login")); formClear(); }; return ( <TouchableWithoutFeedback onPress={Keyboard.dismiss} style={styles.containerWrapper} > <SafeAreaView style={styles.containerWrapper}> <View style={styles.container}> <View style={styles.headerContainer}> <Text variant="displayLarge" style={styles.headerLabel}> Register </Text> </View> <View style={styles.formContainer}> <TextInput label="Email" placeholder="Email" value={email} autoCapitalize="none" onChange={(e: NativeSyntheticEvent<TextInputChangeEventData>) => { setEmail(e.nativeEvent.text); }} /> <TextInput label="Password" placeholder="Password" value={password} autoCapitalize="none" secureTextEntry={passwordVisibility} onChange={(e: NativeSyntheticEvent<TextInputChangeEventData>) => { setPassword(e.nativeEvent.text); }} right={ <TextInput.Icon icon="eye" onPress={() => { setPasswordVisibility((previousState) => !previousState); }} /> } /> <TextInput label="Confirm Password" placeholder="Re-Enter Password" value={confirmPassword} autoCapitalize="none" secureTextEntry={confirmPasswordVisibility} onChange={(e: NativeSyntheticEvent<TextInputChangeEventData>) => { setConfirmPassword(e.nativeEvent.text); }} right={ <TextInput.Icon icon="eye" onPress={() => { setConfirmPasswordVisibility( (previousState) => !previousState ); }} /> } /> </View> <View style={styles.buttonContainer}> <Button mode="contained" onPress={registerHandler}> Register </Button> <Button mode="outlined" onPress={loginHandler}> Already have an Account? Login </Button> </View> </View> </SafeAreaView> </TouchableWithoutFeedback> ); }; export default Register; const styles = StyleSheet.create({ containerWrapper: { flex: 1, }, container: { flex: 1, }, headerContainer: { height: 80, }, headerLabel: {}, inputContainer: { gap: 20, }, buttonContainer: {}, button: {}, formContainer: { flex: 1, gap: 20, }, });
解决方案
核心问题
Login页是作为UserProvider的fallback组件渲染的,不属于导航栈的路由配置,因此useNavigation获取的导航实例无法识别"Register"路由。
修改步骤
1. 调整App.tsx组件层级
将NavigationContainer移入Realm的Provider内部,同时把UserProvider的fallback替换为Loading组件:
import "react-native-gesture-handler"; import { NavigationContainer } from "@react-navigation/native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import NativeStack from "./src/routes/NativeStack"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { PaperProvider } from "react-native-paper"; import { AppProvider, UserProvider } from "@realm/react"; import { appId, baseUrl } from "./atlasConfig.json"; import realmContext from "./src/data/dbContext"; import Loading from "./src/screens/loadingScreen/Loading"; export default function App() { const { RealmProvider } = realmContext; return ( <PaperProvider> <SafeAreaProvider> <GestureHandlerRootView style={{ flex: 1 }}> <AppProvider id={appId} baseUrl={baseUrl}> <UserProvider fallback={<Loading />}> <RealmProvider sync={{ flexible: true, onError: (_, error) => { console.log(error); }, initialSubscriptions: { update(subs, realm) { subs.add(realm.objects("Company")), subs.add(realm.objects("Purchase")), subs.add(realm.objects("Payment")); }, }, }} fallback={<Loading />} > <NavigationContainer> <NativeStack /> </NavigationContainer> </RealmProvider> </UserProvider> </AppProvider> </GestureHandlerRootView> </SafeAreaProvider> </PaperProvider> ); }
2. 更新NativeStack路由配置
在路由中根据用户登录状态渲染Auth栈或主应用栈,确保Login和Register都在导航栈内:
// src/routes/NativeStack.tsx import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { useUser } from "@realm/react"; import Login from "../screens/authScreen/Login"; import Register from "../screens/authScreen/Signup"; // 导入你的主应用页面,比如Home const Stack = createNativeStackNavigator(); const NativeStack = () => { const user = useUser(); return ( <Stack.Navigator> {!user ? ( // 未登录时显示Auth页面 <> <Stack.Screen name="Login" component={Login} /> <Stack.Screen name="Register" component={Register} /> </> ) : ( // 已登录时显示主应用页面 <Stack.Screen name="Home" component={Home} /> // 添加其他主应用路由 )} </Stack.Navigator> ); }; export default NativeStack;
3. 修正Login.tsx的跳转逻辑和标题
- 跳转改为
navigate(或保持replace,确保路由存在即可) - 修正页面标题错误:
// Login.tsx中修改registerHandler和标题 const registerHandler = () => { nav.navigate("Register"); formClear(); }; // 页面标题修正 <Text variant="titleLarge" style={styles.headerLabel}> Login </Text>
内容的提问来源于stack exchange,提问作者Revanth Shalon
相关产品推荐
相关产品推荐

