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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 01:44:52