如何修复React Native登录时Redux dispatch间的状态突变错误?
问题描述
点击登录按钮时出现以下错误,无法定位原因及解决方法:
错误信息:Invariant failed: 在两次dispatch之间检测到状态突变,路径为 'auth.user._2'。这可能导致错误行为。
相关代码
LoginScreen
import React, {useState, useLayoutEffect} from 'react'; import {Alert, Image, Text, TouchableOpacity, View, StyleSheet} from 'react-native'; import {TextInput} from 'react-native-paper'; import Container from '../components/Container'; import Content from '../components/Content'; import { useNavigation } from "@react-navigation/native"; import { login } from "./../actions/auth"; import { useDispatch } from "react-redux"; const LoginScreen = ({navigation}) => { const navigationTo = useNavigation(); const [passwordVisible, setPasswordVisible] = useState(true); const [email, setEmail] = useState(); const [password, setPassword] = useState(); const [loader, setLoader] = useState(false); const [loginText, setLoginText] = useState('Sign In'); const dispatch = useDispatch(); const onConnect = () => { let userLogin = { email: email, password: password, }; if(email && password) { setLoader(true); setLoginText('Sign In...'); dispatch(login(userLogin)) .then((response) => { if (response.status == "success") { navigation.replace("Home"); } }) .catch((error) => { setLoader(false); alert('Your credentials are invalid!'); setLoginText('Sign In'); }); } }; useLayoutEffect(() => { navigation.setOptions({ headerShown: false, }); }, []); return ( <Container insets={{top: true, bottom: true}}> <Content> <View style={{flex: 1}}> <View style={styles.topContainer}> <View style={{flexDirection: 'row', alignItems: 'center'}}></View> <Image style={styles.logo} source={require('../assets/xorixBlack.png')} /> </View> <View style={styles.keyboardView}> <TextInput theme={{colors: {text: '#000'}}} placeholder="Email address" placeholderTextColor="black" selectionColor="black" style={styles.textInput} textColor={'black'} value={email} onChangeText={(text) => setEmail(text)} /> <TextInput theme={{colors: {text: 'black'}}} placeholder="Password" placeholderTextColor="black" value={password} onChangeText={(text) => setPassword(text)} style={styles.textInput} selectionColor="black" secureTextEntry={passwordVisible} textColor={'black'} right={ <TextInput.Icon color={'black'} name={passwordVisible ? 'eye-off' : 'eye'} onPress={() => setPasswordVisible(!passwordVisible)} /> } /> <TouchableOpacity onPress={() => onConnect()} disabled={loader} style={styles.loginBtn}> <Text style={styles.loginText}>{loginText}</Text> </TouchableOpacity> <View style={{alignItems: 'center', padding: 10}}> <View style={styles.text}> <Text style={{fontSize: 12, color: 'black'}}> Forgot your password?{' '} </Text> <Text style={styles.help}> Click here to reset</Text> </View> <View style={styles.seperatorStyle}> <View style={styles.seperator} /> <Text style={{color: 'black'}}> OR </Text> <View style={styles.seperator} /> </View> <View style={styles.facebook}> <TouchableOpacity onPress={() => navigationTo.navigate("Home")}> <Text style={styles.faceText}>Sign Up</Text> </TouchableOpacity> </View> </View> </View> </View> </Content> </Container> ); }; export default LoginScreen;
actions/auth.js
import { LOGIN_SUCCESS, LOGOUT } from "./type"; import AuthService from "../services/authService"; export const login = (user) => (dispatch) => { return AuthService.logIn(user).then( (response) => { console.log(response) if (response.status === "success") { dispatch({ type: LOGIN_SUCCESS, payload: { user: response.user }, }); Promise.resolve(); return response; } }, (error) => { const message = error.toString(); Promise.reject(); return message; } ); }; export const logout = () => (dispatch) => { return AuthService.logOut().then((response) => { if (response.status === "success") { dispatch({ type: LOGOUT, }); Promise.resolve(); return response; } }); };
services/authService.js
import AsyncStorage from "@react-native-async-storage/async-storage"; const logIn = async (user) => { console.log("user info", user); const { email, password } = user; return fetch('https://myapi.com/api/orders/login.json', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }, body: JSON.stringify({ "email": email, "password": password }) }) .then((response) => response.json()) .then((responseJson) => { if(responseJson.json.error === false) { console.log('successfulll'); AsyncStorage.setItem("user", JSON.stringify(responseJson.json.user)); return { status: "success", message: "You are redirecting to home page", user: email, }; } else { return { status: "error", message: "Wrong credentials" }; } }) .catch((error) => { return { status: "error", message: "API call failed" }; }); }; const logOut = async () => { AsyncStorage.clear(); return { status: "success", message: "You are logged out", }; }; export default { logIn, logOut, };
authSlice.js
import { LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT } from "../actions/type"; import AsyncStorage from "@react-native-async-storage/async-storage"; const user = AsyncStorage.getItem("user"); const initialState = user ? { isLoggedIn: true, user } : { isLoggedIn: false, user: null }; export default auth = (state = initialState, action) => { const { type, payload } = action; switch (type) { case LOGIN_SUCCESS: console.log('callled') return { ...state, isLoggedIn: true, user: payload.user, }; case LOGIN_FAILED: return { ...state, isLoggedIn: false, user: null, }; case LOGOUT: return { ...state, isLoggedIn: false, user: null, }; default: return state; } };
store.js
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"; import basketReducer from "./features/basketSlice"; import restaurantReducer from "./features/restaurantSlice"; import productReducer from "./features/productSlice"; import authReducer from "./features/authSlice"; import thunk from "redux-thunk"; export const store = configureStore({ reducer: { basket: basketReducer, restaurant: restaurantReducer, product: productReducer, auth: authReducer }, middleware: [thunk, ...getDefaultMiddleware()], })
错误原因及修复方案
核心原因
Redux检测到状态突变是因为authSlice.js中直接将异步操作的Promise对象赋值给了初始状态的user字段。AsyncStorage.getItem("user")返回的是Promise,而非实际用户数据,Redux追踪状态时,Promise内部属性(如_2)被修改,触发了状态突变警告。
此外还有几处逻辑问题需要同步修复:
1. 修复authSlice的初始状态与用户初始化逻辑
import { LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT } from "../actions/type"; import AsyncStorage from "@react-native-async-storage/async-storage"; // 初始状态默认未登录,用户数据为null const initialState = { isLoggedIn: false, user: null }; export default function auth(state = initialState, action) { const { type, payload } = action; switch (type) { case LOGIN_SUCCESS: console.log('called') return { ...state, isLoggedIn: true, user: payload.user, }; case LOGIN_FAILED: return { ...state, isLoggedIn: false, user: null, }; case LOGOUT: return { ...state, isLoggedIn: false, user: null, }; default: return state; } } // 新增异步action用于启动时初始化用户状态 export const initializeAuth = () => async (dispatch) => { try { const userStr = await AsyncStorage.getItem("user"); if (userStr) { const user = JSON.parse(userStr); dispatch({ type: LOGIN_SUCCESS, payload: { user } }); } } catch (error) { console.error("初始化用户状态失败", error); } };
在App启动组件中调用初始化action:
import { useDispatch } from 'react-redux'; import { initializeAuth } from './features/authSlice'; function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(initializeAuth()); }, [dispatch]); // 其他组件逻辑 }
2. 修复authService的登录数据一致性
登录成功时返回实际用户数据,而非邮箱:
const logIn = async (user) => { console.log("user info", user); const { email, password } = user; return fetch('https://myapi.com/api/orders/login.json', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=UTF-8', "Accept": "application/json" }, body: JSON.stringify({ "email": email, "password": password }) }) .then((response) => response.json()) .then(async (responseJson) => { if(responseJson.json.error === false) { console.log('successfulll'); const userData = responseJson.json.user; await AsyncStorage.setItem("user", JSON.stringify(userData)); return { status: "success", message: "正在跳转至首页", user: userData, }; } else { return { status: "error", message: "Wrong credentials" }; } }) .catch((error) => { return { status: "error", message: "API call failed" }; }); };
3. 修复auth action的Promise处理逻辑
移除无效的Promise.resolve()/Promise.reject()调用,正确传递Promise状态:
export const login = (user) => (dispatch) => { return AuthService.logIn(user).then( (response) => { console.log(response) if (response.status === "success") { dispatch({ type: LOGIN_SUCCESS, payload: { user: response.user }, }); return response; } return Promise.reject(response.message); }, (error) => { const message = error.toString(); return Promise.reject(message); } ); };
4. 修复store的中间件配置
Redux Toolkit默认包含thunk,无需重复添加:
export const store = configureStore({ reducer: { basket: basketReducer, restaurant: restaurantReducer, product: productReducer, auth: authReducer }, middleware: getDefaultMiddleware() })
内容的提问来源于stack exchange,提问作者Brian Millot
相关产品推荐
相关产品推荐

