登录后需重载App才显示正确页面的问题排查
问题:登录后需重启App才会切换到认证页面
控制台显示已生成token,但isAuthenticated仍为false,必须重载App才能让状态更新并渲染ExpensesOverview页面。需求是在expenses-context.js中全局管理isAuthenticated状态。
相关代码片段
App.js
function ExpensesOverview() { return ( ... <BottomTabs.Screen name='RecentExpenses' component={RecentExpenses} /> <BottomTabs.Screen name='AllExpenses' component={AllExpenses} /> </BottomTabs.Navigator> ) } export default function App() { const [isAuthenticated, setIsAuthenticated] = useState(false); useEffect(() => { const checkAuthentication = async () => { try { const token = await AsyncStorage.getItem('userToken'); console.log('Token from AsyncStorage:', token); if (token) { setIsAuthenticated(true); } } catch (error) { console.error('Error checking authentication:', error); } }; checkAuthentication(); }, []); return ( <> <StatusBar style="dark" /> <ExpensesContextProvider> <NavigationContainer> <Stack.Navigator> {isAuthenticated ? ( <> <Stack.Screen name='ExpensesOverview' component={ExpensesOverview} options={{ headerShown: false }} /> <Stack.Screen name='ManageExpense' component={ManageExpense} options={{ presentation: 'modal' }} /> </> ) : ( <Stack.Screen name="LoginScreen" component={LoginScreen} options={{ headerShown: false }} /> )} </Stack.Navigator> </NavigationContainer> </ExpensesContextProvider> </> ); }
LoginScreen.js
function LoginScreen() { const expensesCtx = useContext(ExpensesContext); const [credentials, setCredentials] = useState({ email: '', password: '' }); async function loginHandler({ email, password }) { try { const response = await expensesCtx.loginUser({ email, password }); console.log('Response:', response.token); if (response) { const {token} = response; console.log('LoginScreen token:', token); await AsyncStorage.setItem('userToken', JSON.stringify(token)); await expensesCtx.updateIsAuthenticated(true); } else { console.error('Invalid response data:', response); } } catch (error) { console.error('Error logging in:', error); } } return ( <SafeAreaView style={styles.container}> <AuthContent isLogin={true} onSignUp={signUpHandler} onLogin={loginHandler} /> <View style={styles.registerButtonContainer}> </View> </SafeAreaView> ); } export default LoginScreen;
expenses-context.js
export const ExpensesContext = createContext({ loginUser: ({ email, password }) => { }, isAuthenticated: false, }) function expensesReducer(state, action) { case 'LOGIN_USER': return { ...state, isAuthenticated: true, } default: return state; } } function ExpensesContextProvider({ children }) { const [expensesState, dispatch] = useReducer(expensesReducer, []); const [isAuthenticated, setIsAuthenticated] = useState(false) useEffect(()=>{ const checkAuthentication = async () => { try { const token = await AsyncStorage.getItem('email') if (token) { setIsAuthenticated(true) } } catch (error) { console.error('Error checking authentication:', error) } } checkAuthentication() }, []) const updateIsAuthenticated = (value) => { setIsAuthenticated(value); }; async function loginUser({ email, password }) { try { const response = await axios.post('http://localhost:8082/authUser', { email, password, }); const { token } = response.data; await AsyncStorage.setItem('userToken', JSON.stringify(token)); setIsAuthenticated(true); dispatch({ type: 'LOGIN_USER', payload: response.data.token }); return response.data } catch (error) { console.error('Error logging in:', error); } } const value = { loginUser: loginUser, isAuthenticated: isAuthenticated, updateIsAuthenticated: updateIsAuthenticated, }; return ( <ExpensesContext.Provider value={value}> {children} </ExpensesContext.Provider> ); } export default ExpensesContextProvider;
问题根源
- 状态分散:App组件自己维护了
isAuthenticated状态,和Context中的状态完全独立。登录时更新的是Context里的状态,App组件的状态未同步,因此不会触发页面切换。 - 认证检查逻辑错误:Context中启动时读取的是
email而非userToken,导致无法正确识别已登录状态。 - 冗余操作:LoginScreen重复存储token并手动更新状态,和Context的
loginUser功能重叠,且未同步App组件的状态。 - Reducer语法缺失:
expensesReducer漏写了switch语句,无法正确处理LOGIN_USERaction。
解决方案
1. 统一状态管理(修改App.js)
移除App组件自身的状态,直接从Context读取isAuthenticated:
import { useContext } from 'react'; import { ExpensesContext } from './expenses-context'; // ... ExpensesOverview 组件保持不变 export default function App() { const { isAuthenticated } = useContext(ExpensesContext); return ( <> <StatusBar style="dark" /> <ExpensesContextProvider> <NavigationContainer> <Stack.Navigator> {isAuthenticated ? ( <> <Stack.Screen name='ExpensesOverview' component={ExpensesOverview} options={{ headerShown: false }} /> <Stack.Screen name='ManageExpense' component={ManageExpense} options={{ presentation: 'modal' }} /> </> ) : ( <Stack.Screen name="LoginScreen" component={LoginScreen} options={{ headerShown: false }} /> )} </Stack.Navigator> </NavigationContainer> </ExpensesContextProvider> </> ); }
2. 修复Context逻辑(修改expenses-context.js)
用Reducer统一管理认证状态,修复启动检查逻辑:
import { createContext, useReducer, useEffect } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import axios from 'axios'; export const ExpensesContext = createContext({ loginUser: ({ email, password }) => { }, isAuthenticated: false, }) function expensesReducer(state, action) { switch(action.type) { case 'LOGIN_USER': return { ...state, isAuthenticated: true }; case 'CHECK_AUTH': return { ...state, isAuthenticated: action.payload }; default: return state; } } function ExpensesContextProvider({ children }) { const [expensesState, dispatch] = useReducer(expensesReducer, { isAuthenticated: false }); useEffect(()=>{ const checkAuthentication = async () => { try { const token = await AsyncStorage.getItem('userToken'); dispatch({ type: 'CHECK_AUTH', payload: !!token }); } catch (error) { console.error('Error checking authentication:', error); dispatch({ type: 'CHECK_AUTH', payload: false }); } } checkAuthentication(); }, []); async function loginUser({ email, password }) { try { const response = await axios.post('http://localhost:8082/authUser', { email, password }); const { token } = response.data; await AsyncStorage.setItem('userToken', JSON.stringify(token)); dispatch({ type: 'LOGIN_USER' }); return response.data; } catch (error) { console.error('Error logging in:', error); } } const value = { loginUser: loginUser, isAuthenticated: expensesState.isAuthenticated, }; return ( <ExpensesContext.Provider value={value}> {children} </ExpensesContext.Provider> ); } export default ExpensesContextProvider;
3. 简化LoginScreen操作(修改LoginScreen.js)
移除冗余的token存储和状态更新:
function LoginScreen() { const expensesCtx = useContext(ExpensesContext); const [credentials, setCredentials] = useState({ email: '', password: '' }); async function loginHandler({ email, password }) { try { const response = await expensesCtx.loginUser({ email, password }); if (!response) { console.error('Invalid response data:', response); } } catch (error) { console.error('Error logging in:', error); } } return ( <SafeAreaView style={styles.container}> <AuthContent isLogin={true} onSignUp={signUpHandler} onLogin={loginHandler} /> <View style={styles.registerButtonContainer}> </View> </SafeAreaView> ); } export default LoginScreen;
关键修改点
- 将认证状态完全移至Context的Reducer中,实现全局统一管理
- 修复Context启动时的token读取逻辑,确保正确识别已登录状态
- 移除App组件的独立状态,直接依赖Context状态触发页面切换
- 删除LoginScreen中的冗余操作,所有登录逻辑统一由Context处理
内容的提问来源于stack exchange,提问作者codereyes
相关产品推荐
相关产品推荐

