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

React Native Context值变更后未触发组件重渲染问题排查

React Native导航栈切换不生效问题

我在开发React Native登录系统,用Context做状态管理,通过userId判断切换导航栈:存在userId显示AppStack(应用主页面),否则显示AuthStack(登录/注册页面)。

点击登录按钮并通过验证后,Context内的userId已成功更新,但负责导航切换的AppNav组件没有自动重渲染。用useEffect监测各组件的userId状态,确认Context里的userId确实变了,但AppNav没响应;手动重渲染Context文件能触发导航栈切换,重渲染AppNav文件则无效。

Context代码

import React, { useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Alert } from "react-native";

export const AppContext = React.createContext(true);

export function Context({ children }) {
  const [userId, setUserId] = useState();
  const [isLoading, setisLoading] = useState(false);

  /**
   * The Lists variable is an array of productList which the list that is shown in the MyListScreen
   * Form -> [{key, name, list:[{ id, product:{name, quantity, quantityType, price}, buy: boolean }] }]
   */
  const [Lists, setLists] = useState([]);

  /**
   * Visibility of the modal
   */
  const [showModal, setShowModal] = useState(false);

  //don't forget to add the Login properties

  useEffect(() => {
    const fetchData = async () => {
      try {
        await isLoggedIn();
        await checkLists();
        console.log("Context -> Lists' length: " + Lists.length);
        console.log("Context -> Lists: " + JSON.stringify(Lists));
      } catch (error) {
        console.log("Error fetching data: ", error);
      }
    };

    fetchData();
    console.log("Context -> useEffect: userId: " + userId);
  }, []);

  useEffect(() => {
    console.log("Context -> Login changed: " + userId);
  }, [userId]);

  //For the Login
  const isLoggedIn = async () => {
    try {
      setisLoading(true);
      let userToken = await AsyncStorage.getItem("userId");
      setUserId(userToken);
      setisLoading(false);
      return 0; // Success
    } catch (error) {
      console.log("Error in line 24 Context.js", error);
      return 1; // Error
    }
  };

  const login = async (userId) => {
    try {
      setisLoading(false);
      setUserId(JSON.stringify(userId));
      AsyncStorage.setItem("userId", "mamammamama");
      console.log("Context -> userId: " + userId);
      return 0; // Success
    } catch (error) {
      console.log("handleSignupError in login:", error);
      return 1; // Error
    }
  };

  const logout = async () => {
    try {
      setisLoading(true);
      setUserId(null);
      await AsyncStorage.removeItem("userId");
      setisLoading(false);
      console.log("Context -> Logged out: " + userId);
      return 0; // Success
    } catch (error) {
      console.log("Error in logout:", error);
      return 1; // Error
    }
  };

  // 省略其他未展示的函数

  return (
    <AppContext.Provider
      value={{
        userId,
        isLoading,
        login,
        logout,
        Lists,
        saveLists,
        addProduct,
        removeItem,
        saveProductList,
        toggleCheck,
      }}
    >
      {children}
    </AppContext.Provider>
  );
}

LoginScreen代码

import React, { useContext, useEffect, useState } from "react";
import {
  SafeAreaView,
  Text,
  Image,
  View,
  TouchableOpacity,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import InputField from "../Components/InputField";
import Button from "../Components/Generic/Button";
import { AppContext } from "../Contexts/Context";
import { ScaledSheet } from "react-native-size-matters";
import genericStyles from "../Styles/genericStyles";

/**
 * The login screen component.
 *
 * @param {Object} navigation - The navigation object provided by React Navigation.
 * @returns {JSX.Element} The login screen UI.
 */
export default function LoginScreen({ navigation }) {
  const { login } = useContext(AppContext);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const [lgin, setLgin] = useState(false);

  useEffect(() => {
    if (lgin) {
      login("mama");
      setLgin(false);
    }
  }, [lgin]);

  /**
   * Checks if an email is valid using a regular expression.
   *
   * @param {string} email - The email to validate.
   * @returns {boolean} True if the email is valid, false otherwise.
   */
  function isValidEmail(email) {
    const emailRegex =
      /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return emailRegex.test(String(email).toLowerCase());
  }

  /**
   * Handles the login action. Validates input and attempts to log in.
   */
  const handleSignup = () => {
    if (!email || !password) {
      alert("Please fill out all the fields");
    } else if (!isValidEmail(email)) {
      alert("Please enter a valid email address");
    } else {
      setLgin(true);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.centeredContainer}>
        <View style={styles.centered}>
          <Image
            source={require("../Assets/Images/Logos/discountlogo.png")}
            style={styles.logo}
          />
        </View>

        <Text style={styles.title}>Login</Text>

        {/* Email Input */}
        <InputField
          label="Email"
          icon={
            <Ionicons name="at" size={20} color="#666" style={styles.icon} />
          }
          keyboardType="email-address"
          onChangeText={(text) => {
            setEmail(text);
          }}
        />

        {/* Password Input */}
        <InputField
          label="Password"
          inputType="password"
          icon={
            <Ionicons
              name="lock-closed-outline"
              size={20}
              color="#666"
              style={styles.icon}
            />
          }
          onChangeText={(text) => setPassword(text)}
        />

        <Button
          label="Login"
          onPress={handleSignup}
          addStyles={styles.loginButton}
        />
        <Text style={styles.centerText}>Or, login with ...</Text>

        {/* 省略其他未展示的UI部分 */}
      </View>
    </SafeAreaView>
  );
}

AppNav代码

import { NavigationContainer } from "@react-navigation/native";
import { useContext, useEffect } from "react";
import { AppContext, Context } from "../Contexts/Context";
import AppStack from "../Navigation/AppStack";
import AuthStack from "../Navigation/AuthStack";

function AppNav() {
  const { userId } = useContext(AppContext);

  useEffect(() => {
    console.log("UserId in useEffect:", userId);
  }, [userId]);

  return (
    <Context>
      <NavigationContainer>
        {userId ? <AppStack /> : <AuthStack />}
      </NavigationContainer>
    </Context>
  );
}

export default AppNav;

问题原因及解决方法

核心问题

AppNav组件在Context Provider的外层调用useContext(AppContext),此时获取的是Context的默认值,而非Provider提供的实时更新值。因为Context是AppNav的子组件,AppNav处于Provider层级之上,无法监听到内部状态变化。

修复步骤

  1. 调整组件层级:将消费Context的逻辑移到Provider内部的子组件中,确保能正确接收状态更新。

修改后的AppNav代码:

import { NavigationContainer } from "@react-navigation/native";
import { useContext, useEffect } from "react";
import { AppContext, Context } from "../Contexts/Context";
import AppStack from "../Navigation/AppStack";
import AuthStack from "../Navigation/AuthStack";

// 内部组件负责消费Context并渲染导航栈
function NavContent() {
  const { userId } = useContext(AppContext);

  useEffect(() => {
    console.log("UserId in useEffect:", userId);
  }, [userId]);

  return (
    <NavigationContainer>
      {userId ? <AppStack /> : <AuthStack />}
    </NavigationContainer>
  );
}

function AppNav() {
  return (
    <Context>
      <NavContent />
    </Context>
  );
}

export default AppNav;
  1. 额外优化点:
    • 登录函数中AsyncStorage.setItem的参数与setUserId的值不一致,建议统一:
      const login = async (userId) => {
        try {
          setisLoading(false);
          const userIdStr = JSON.stringify(userId);
          setUserId(userIdStr);
          await AsyncStorage.setItem("userId", userIdStr);
          console.log("Context -> userId: " + userId);
          return 0; // Success
        } catch (error) {
          console.log("handleSignupError in login:", error);
          return 1; // Error
        }
      };
      
    • 将初始userId状态设为null,使判断逻辑更明确:
      const [userId, setUserId] = useState(null);
      

内容的提问来源于stack exchange,提问作者Aissa Rouk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 18:54:55