You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:45:56