Redux Toolkit登出功能触发后报错被拒绝的问题排查
Redux Toolkit登出功能dispatch被拒绝的问题修复
问题描述
我正在使用Redux Toolkit开发登出功能,但在dispatchlogoutUser函数时操作被拒绝,报错信息为cannot read property of undefined。需求是仅当用户处于已认证状态(isAuthenticated为true)时,在导航栏渲染登出按钮,点击按钮触发登出逻辑。
组件代码
import React from 'react' import { Container, AppBar, Toolbar, styled, InputBase, Button } from '@mui/material' import { useDispatch, useSelector } from 'react-redux' import { logoutUser } from '../../reducers/auth/authSlice' const Search = styled("div")(({theme}) => ({ backgroundColor: "white", padding: "10px", borderRadius: "10px", width: "40%" })) export default function Navbar() { const dispatch = useDispatch(); const isAuthenticated = useSelector(state => state.user.isAuthenticated); const handleLogout = () => { dispatch(logoutUser()); console.log(isAuthenticated) }; return ( <AppBar position='static' color='primary'> <Container> <Toolbar> <Search> <InputBase placeholder='Search...' fullWidth={true}/> </Search> {isAuthenticated ? ( <Button type='submit' variant="contained" color="primary" onClick={handleLogout} > Logout </Button> ) : ''} </Toolbar> </Container> </AppBar> ) }
Slice代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import setAuthToken from '../../utils/setAuthToken'; import axios from 'axios'; const initialState = { token: localStorage.getItem('item'), isAuthenticated: false, loading: true, user: null, error: null } export const loadUser = createAsyncThunk( 'user/loadUser', async ({ rejectWithValue }) => { if(localStorage.token){ setAuthToken(localStorage.token); } try { const res = axios.get('http://localhost:3000/api/auth'); return res.data; } catch (error) { return rejectWithValue(error.response.data); } } ) export const registerUser = createAsyncThunk( 'user/registerUser', async (newUser, { rejectWithValue }) => { try { const config = { headers: { 'Content-Type': 'application/json', }, }; const body = JSON.stringify(newUser); const response = await axios.post( 'http://localhost:3000/api/users', body, config ); return response.data; } catch (error) { return rejectWithValue(error.response.data); } } ); export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { dispatch, rejectWithValue }) => { const body = { email, password }; const config = { headers: { 'Content-Type': 'application/json', }, }; try { const res = await axios.post('http://localhost:3000/api/auth', body, config); dispatch(loadUser()); console.log(body) return res.data; } catch (err) { return rejectWithValue(err.response.data); } }); export const logoutUser = createAsyncThunk( 'auth/logout', async (_, { getState, rejectWithValue }) => { try { localStorage.removeItem('token'); const state = getState(); state.user.isAuthenticated = false; state.user.token = null; state.user.user = null; return true; } catch (err) { return rejectWithValue(err.response.data); } } ); export const userSlice = createSlice({ name: 'user', initialState, reducers: {}, extraReducers: { [registerUser.pending]: (state) => { state.loading = true; state.error = null; }, [registerUser.fulfilled]: (state, action) => { state.user = action.payload; state.loading = false; state.error = null; }, [registerUser.rejected]: (state, action) => { state.loading = false; state.error = action.payload; }, [loadUser.pending]: (state) => { state.loading = true; state.error = null; }, [loadUser.fulfilled]: (state, action) => { state.isAuthenticated = true; state.user = action.payload; state.loading = false; state.error = false; }, [loadUser.rejected]: (state, action) => { state.loading = false; state.isAuthenticated = false; state.error = action.payload; }, [loginUser.pending]: (state) => { state.loading = true; state.error = null; }, [loginUser.fulfilled]: (state, action) => { state.token = localStorage.setItem('token', action.payload.token); state.isAuthenticated = true; state.loading = false; state.user = action.payload.user; }, [loginUser.rejected]: (state, action) => { state.isAuthenticated = false; state.loading = false; state.error = action.payload; }, [logoutUser.pending]: (state) => { state.loading = true; state.error = null; }, [logoutUser.fulfilled]: (state) => { state.loading = false; state.isAuthenticated = false; state.token = null; state.user = null; }, [logoutUser.rejected]: (state, action) => { state.loading = false; state.error = action.payload; } } }) export const { } = userSlice.actions export default userSlice.reducer
错误原因分析
- 直接修改Redux State:
logoutUser中通过getState()获取状态后直接修改属性,违反Redux不可变状态原则,导致状态异常。 - 异步请求未等待:
loadUser里的axios.get未加await,返回Promise而非响应数据,后续处理出错。 - LocalStorage键名不匹配:initialState中
localStorage.getItem('item')应该是'token',与登录逻辑的键名不一致。 - 错误处理未做边界判断:logout无API请求,
catch中err.response.data因err.response不存在抛出cannot read property of undefined,这就是核心报错原因。 loadUser参数错误:第一个参数是传入的参数,第二个才是thunkAPI,原代码把thunkAPI解构到第一个参数,导致rejectWithValue为undefined。
修复方案
1. 修正logoutUser异步逻辑
移除直接修改state的代码,状态更新交给extraReducers处理,同时重置auth token配置:
export const logoutUser = createAsyncThunk( 'auth/logout', async (_, { rejectWithValue }) => { try { localStorage.removeItem('token'); setAuthToken(null); // 重置请求头的token return true; } catch (err) { return rejectWithValue(err.message || '登出失败'); } } );
2. 修复loadUser的参数与异步等待
修正参数结构,给axios.get加上await,并添加可选链避免错误:
export const loadUser = createAsyncThunk( 'user/loadUser', async (_, { rejectWithValue }) => { if(localStorage.token){ setAuthToken(localStorage.token); } try { const res = await axios.get('http://localhost:3000/api/auth'); return res.data; } catch (error) { return rejectWithValue(error.response?.data || error.message); } } );
3. 修正initialState的LocalStorage键名
const initialState = { token: localStorage.getItem('token'), isAuthenticated: false, loading: true, user: null, error: null }
4. 修正loginUser的token存储逻辑
localStorage.setItem返回undefined,需先存储再赋值给state:
[loginUser.fulfilled]: (state, action) => { localStorage.setItem('token', action.payload.token); state.token = action.payload.token; state.isAuthenticated = true; state.loading = false; state.user = action.payload.user; },
5. 优化所有错误处理的边界判断
在rejected回调中添加默认值,避免payload为undefined:
[loadUser.rejected]: (state, action) => { state.loading = false; state.isAuthenticated = false; state.error = action.payload || '加载用户信息失败'; }, [logoutUser.rejected]: (state, action) => { state.loading = false; state.error = action.payload || '登出操作失败'; }
内容的提问来源于stack exchange,提问作者tukitooo1998
相关产品推荐
相关产品推荐

