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

React Redux createAsyncThunk Action无法更新状态问题求助

问题:Redux状态未更新,尽管API返回了用户数据

我正在为Node.js API实现注册页面,使用createAsyncThunk实现获取用户数据并更新Redux状态的功能。API运行正常,派发action时确实会返回创建的用户数据,但用户状态并未更新,始终保持初始状态。

控制台截图

控制台截图

相关代码

User Slice 代码

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

const initialState = { 
    username: null,
    email: null,
    password: null,
    gamesPlayed: null,
    gamesWon: null,
    gamesLost: null,
    draws: null,
    loading: false,
    error: null    
 }

 const serializeAndStringify = obj => {
    const seen = []
    JSON.stringify(
        obj,
        (key, val) => {
            if(val != null && typeof val === 'object') {
                if(seen.indexOf(val) >= 0)
                   return
                seen.push(val)
            }
            return val                
        }
    )
    return seen[0]
}

export const fetchUserData = (requestUrl, userData) => 
    createAsyncThunk(
        'userAuth',
        async () => 
            axios.post(requestUrl, serializeAndStringify(userData))
    )

const userSlice = createSlice({
    name: 'user',
    initialState,
    reducers: {
        victory (state) {
            state.gamesPlayed++
            state.gamesWon++
        },
        defeat (state) {
            state.gamesPlayed++
            state.gamesLost++
        },
        draw (state) {
            state.gamesPlayed++
            state.draws++
        }         
    },
    extraReducers: {
        [fetchUserData.pending]: (state, action) => {
            state.loading = true
        },
        [fetchUserData.fullFilled]: (state, { payload }) => {
            state.loading = false
            state.username = payload.data.username
            state.email = payload.data.email
            state.password = payload.data.password             
        },
        [fetchUserData.rejected]: (state, action) => {
            state.loading = false
            state.error = action.payload
        }
    }
})

export default userSlice.reducer

store.js 代码

import { configureStore } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

import userReducer from './user'

const reducer = {
    user: userReducer
}

const middlewares = [thunk]

if(process.env.NODE_ENV === 'development')
    middlewares.push(logger)  

const store = configureStore({
    reducer,
    middleware: middlewares,
    devTools: process.env.NODE_ENV !== 'production',
})

export default store

注册页面代码

import { Fragment } from 'react'
import { useNavigate } from 'react-router'
import { useDispatch } from 'react-redux'

import { fetchUserData } from '../../redux/user.js'

import './register.styles.scss'

const RegisterPage = () => {
    const navigate = useNavigate()
    const dispatch = useDispatch()

    const handleSubmit = event => {
        event.preventDefault()
        const { username, email, password } = event.target
        let userData
        let requestUrl = '/auth/'

        if(username) {
            userData = { 
                username: username.value, 
                email: email.value, 
                password: password.value 
            }
            requestUrl += 'registration'
        } else {
            userData = {  
                email: email.value,
                password: password.value
            }         
            requestUrl += 'login'   
        }
        dispatch(fetchUserData(requestUrl, userData)())
    }  

    return (
        <Fragment>
        <div className='register-page'>            
            <form className='container' onSubmit={handleSubmit}>
                <span>WELCOME BACK</span>
                <input name='email' required type='email' placeholder='Email' />
                <input name='password' required type='password' minLength='8' placeholder='Password' />
                <div className='button-wrapper'>
                    <button type='submit'>LOG IN</button>
                    <button className='google-button'>
                        <a href='http://localhost:4000/auth/google'>
                            LOG IN WITH GOOGLE
                        </a>
                    </button>
                </div>
            </form>
            <form className='container' onSubmit={handleSubmit}>
                <span>REGISTER</span>
                <input name='username' required type='text'  placeholder='Username' />
                <input name='email' required type='email' placeholder='Email' />
                <input name='password' required type='password' minLength='8' placeholder='Password' />
                <div className='button-wrapper'>
                    <button type='submit'>SIGN IN</button>
                    <button className='google-button'>
                        <a href='http://localhost:4000/auth/google'>
                            SIGN IN WITH GOOGLE
                        </a>
                    </button>
                </div>
            </form>   
        </div>
        <button onClick={() => navigate('/', {replace: true})}>Go back</button>       
        </Fragment>
    )
}

export default RegisterPage

问题原因及修复方案

1. createAsyncThunk 创建方式错误

当前fetchUserData是一个返回新createAsyncThunk实例的函数,每次调用都会生成全新的action类型,导致Redux无法匹配extraReducers中定义的处理逻辑。

修复:
直接创建createAsyncThunk,将请求参数作为payload传入:

export const fetchUserData = createAsyncThunk(
  'user/userAuth', // 加上slice前缀避免命名冲突
  async ({ requestUrl, userData }) => {
    const serializedData = serializeAndStringify(userData);
    // 必须await请求获取响应数据
    const response = await axios.post(requestUrl, serializedData);
    return response.data; // 返回后端用户数据,后续payload直接取该值
  }
);

2. extraReducers 拼写错误

fullFilled应为fulfilled(少一个字母l),导致成功的action无法被捕获处理。

修复:

extraReducers: {
    [fetchUserData.pending]: (state) => {
        state.loading = true;
    },
    // 修正拼写为fulfilled
    [fetchUserData.fulfilled]: (state, { payload }) => {
        state.loading = false;
        // payload直接是response.data,无需再取payload.data
        state.username = payload.username;
        state.email = payload.email;
        state.password = payload.password;
    },
    [fetchUserData.rejected]: (state, action) => {
        state.loading = false;
        state.error = action.error.message; // rejected时错误信息在action.error中
    }
}

3. 组件中dispatch调用错误

当前写法dispatch(fetchUserData(requestUrl, userData)())会每次创建新的thunk实例,无法匹配之前定义的extraReducers。

修复:
直接dispatch thunk并传入参数对象:

dispatch(fetchUserData({ requestUrl, userData }));

4. 可选优化:移除冗余thunk中间件

@reduxjs/toolkit的configureStore已默认包含thunk中间件,无需手动引入添加:

// store.js 简化后
import { configureStore } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import userReducer from './user'

const reducer = {
    user: userReducer
}

const middlewares = []

if(process.env.NODE_ENV === 'development')
    middlewares.push(logger)  

const store = configureStore({
    reducer,
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middlewares),
    devTools: process.env.NODE_ENV !== 'production',
})

export default store

5. 可选:修正serializeAndStringify函数

当前函数逻辑存在问题,JSON.stringify结果未被使用,若用于处理循环引用可简化为:

const serializeAndStringify = obj => {
  const seen = new WeakSet();
  return JSON.parse(JSON.stringify(obj, (key, val) => {
    if (typeof val === 'object' && val !== null) {
      if (seen.has(val)) return;
      seen.add(val);
    }
    return val;
  }));
}

若无循环引用问题,可直接传入userData,无需序列化函数。


内容的提问来源于stack exchange,提问作者Saverio Randazzo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 23:39:20