React+Redux Toolkit调用Node.js认证API无数据返回问题排查
用户认证功能报错排查与修复
我正在实现用户认证功能,Node.js端的POST认证接口通过Postman测试可正常完成用户认证,但在React项目中使用Redux Toolkit实现登录功能时,提交表单出现400 Bad Request错误,API接收为空对象,数据未被正确传递。
Node.js认证接口
router.post('/', [ check('email', 'Please include a valid email').isEmail(), check('password', 'Password is required').exists() ], async (req, res) => { const errors = validationResult(req); if(!errors.isEmpty()){ return res.status(400).json({ errors:errors.array()}); //400 is for bad requests } const { email, password } = req.body; try{ //See if user exists let user = await User.findOne({ email }); if(!user){ return res.status(400).json({ errors: [{ msg:'Invalid credentials' }] }); } //Compare the input password, plain text, to the encrypted password. const isMatch = await bcrypt.compare(password, user.password); if(!isMatch){ return res.status(400).json({ errors: [{ msg:'Invalid credentials' }] }); } //Return jsonwebtoken -> this for users to be logged in right after registration const payload = { user:{ id: user.id } } jwt.sign( payload, config.get('jwtSecret'), {expiresIn: 360000}, //change to 3600 for production (err, token) => { if(err) throw err; res.json({ token }); } ) }catch(err){ console.error(err.message); res.status(500).send('Server Error'); } });
Token设置工具函数
import axios from 'axios' const setAuthToken = token => { if(token){ axios.defaults.headers.common['x-auth-token'] = token; } else { delete axios.defaults.headers.common['x-atuh-token']; } } export default setAuthToken
Redux Toolkit 用户切片
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'); } catch (error) { return rejectWithValue(error.response.data); } } ) export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { dispatch, rejectWithValue }) => { const body = { email, password }; try { const res = await axios.post('http://localhost:3000/api/auth', body); dispatch(loadUser()); return res.data; } catch (err) { return rejectWithValue(err.response.data); } }); export const userSlice = createSlice({ name: 'user', initialState, reducers: {}, extraReducers: { [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; }, [loginUser.rejected]: (state, action) => { state.isAuthenticated = false; state.loading = false; state.error = action.payload; } } }) export const { } = userSlice.actions export default userSlice.reducer
React登录组件
import React, { useState } from 'react' import { useDispatch } from 'react-redux'; import { loginUser } from '../reducers/auth/authSlice'; //Material UI import {Grid, Container, Box, Button, FormControl, InputLabel, Input, FormHelperText } from '@mui/material' function Login({ isAuthenticated }) { const dispatch = useDispatch(); const [formData, setFormData] = useState({ email: '', password: '' }); const { email, password } = formData; const onChange = e => setFormData({ ...formData, [e.target.name]:e.target.value }); const onSubmit = async e => { e.preventDefault(); dispatch(loginUser(email, password)) } return ( <Container> <Box p={4}> <Grid container> <form onSubmit={e => onSubmit(e)}> <Grid item md={12}> <FormControl> <InputLabel htmlFor="email">Email</InputLabel> <Input id='email' type='email' name='email' value={email} onChange={e => onChange(e)} /> <FormHelperText>Insert your email</FormHelperText> </FormControl> </Grid> <Grid item md={12}> <FormControl> <InputLabel htmlFor="password">Password</InputLabel> <Input id="password" type='password' name='password' value={password} onChange={e => onChange(e)} /> <FormHelperText>Insert your password (must have at least 6 characters)</FormHelperText> </FormControl> </Grid> <Grid item md={12}> <Button type='submit' variant="contained" color="primary"> Login </Button> </Grid> </form> </Grid> </Box> </Container> ) } export default Login
核心问题与修复方案
登录组件参数传递错误
错误点:登录组件调用loginUser时传入了两个独立参数email和password,但loginUser异步thunk定义的是接收包含这两个字段的对象参数。
修复:修改onSubmit中的dispatch调用:dispatch(loginUser({ email, password }))setAuthToken函数拼写错误
错误点:删除请求头时将x-auth-token误写为x-atuh-token,导致登出时无法正确移除认证头。
修复:修正拼写:delete axios.defaults.headers.common['x-auth-token'];loadUser异步thunk存在两处错误
- 参数解构错误:
loadUser的第一个参数是payload,不需要解构rejectWithValue,应该从第二个参数(thunkAPI)中解构。 - 未返回响应数据:发送GET请求后没有返回数据,导致fulfilled阶段无法获取用户信息。
修复后的loadUser:
export const loadUser = createAsyncThunk( 'user/loadUser', async (_, { rejectWithValue }) => { if(localStorage.getItem('token')){ setAuthToken(localStorage.getItem('token')); } try { const res = await axios.get('http://localhost:3000/api/auth'); return res.data; } catch (error) { return rejectWithValue(error.response.data); } } )- 参数解构错误:
初始状态localStorage键名错误
错误点:初始状态中读取localStorage.getItem('item'),但实际存储token的键是token,导致初始化时无法读取已保存的token。
修复:修改初始状态:const initialState = { token: localStorage.getItem('token'), // ... 其他状态 }额外优化:loginUser.fulfilled中的token赋值错误
错误点:localStorage.setItem返回undefined,直接赋值给state.token会导致token状态变为undefined。
修复:先存储token,再赋值:[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; },
内容的提问来源于stack exchange,提问作者tukitooo1998
相关产品推荐
相关产品推荐

