React+Redux注册功能报500错误与TypeError问题排查
注册功能报错问题
报错信息
- 控制台抛出错误:
RegisterPage.js:30 Uncaught (in promise) TypeError: Cannot read properties of undefined - 接口500响应内容:
{"err": "E11000 duplicate key error collection: private.users index: email_1 dup key: { email: \"samarkan@mail.com\" }"}
场景:使用Redux实现用户注册、获取用户信息逻辑时,注册请求返回500状态码,附全部相关代码如下,求修复方案。
附原有代码
userReducer.js
import Axios from "axios"; export const register = ({name, fname, phone, email, password}) => async (dispatch)=> { dispatch({type: "USER_REGISTER_REQUEST", payload: {name, fname, phone, email}}); try { const {data} = await Axios.post("api/users/register",{name, fname, phone, email}) dispatch({type: "USER_REGISTER_SUCCESS", payload: data }) } catch (error) { dispatch({type: "USER_REGISTER_FAIL", payload: error.response && error.response.data.message ? error.response.data.message : error.message}) } }; export const registerReducer = (state = {loading: true, userinfo: {}}, action) => { switch(action.type) { case "USER_REGISTER_REQUEST": return {loading: true} case "USER_REGISTER_SUCCESS": return {loading: false, userInfo: action.payload}; case "USER_REGISTER_FAIL": return {loading: false, error: action.payload} default: return state; } };
RegisterPage.js
import React from "react"; import {useDispatch, useSelector} from "react-redux"; import InputGroup from "./common/InputGroup"; import {useState, useEffect} from "react"; import {register} from "../reducers/userReducer"; import {useNavigate, useParams} from "react-router-dom"; const RegisterPage = () => { const dispatch = useDispatch(); const [name, setName] = useState(''); const [fname, setFname] = useState(''); const [phone, setPhone] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const navigate = useNavigate(); const validateEmail = (email) => { return String(email) .toLowerCase() .match( /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ); }; const submitHandler = (e) => { if(password === confirm && validateEmail(email)) { dispatch(register({name, fname, phone, email})).then((user)=> navigate({pathname: "/profile", search: `?id=${user._id}&name=${user.name}&fname=${user.fname}&email=${user.email}`}) ); } else { window.alert("password and confirm password not the same try again or email not conform!") } } return ( <div> <h2>Welcome to private <span>Let's be private</span></h2> <p id="icon" >Pr</p> <div className="form"> <InputGroup value={name} type="text" label="name" name="name" onChange={(e)=> setName(e.target.value)} /> <InputGroup value={fname} type="text" label="fname" name="fname" onChange={(e)=> setFname(e.target.value)}/> <InputGroup value={phone} type="phone" label="phone" name="phone" onChange={(e)=> setPhone(e.target.value)}/> <InputGroup value={email} type="email" label="email" name="email" onChange={(e)=> setEmail(e.target.value)}/> <InputGroup value={password} type="password" label="password" name="password" onChange={(e)=> setPassword(e.target.value)} /> <InputGroup value={confirm} type="password" label="confirm password" name="confirm" onChange={(e)=> setConfirm(e.target.value)} /> <button onClick={(e)=>submitHandler()} >Register</button> </div> </div> ) }; export default RegisterPage;
store.js
import { createStore } from "redux"; import {compose, applyMiddleware} from "redux"; import {combineReducers} from "redux"; import thunk from "redux-thunk"; import {registerReducer} from "./reducers/userReducer"; const initialState = {user: {}}; const reducer = combineReducers({ user: registerReducer }); const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( reducer, initialState, composeEnhancer(applyMiddleware(thunk))) export default store;
问题原因&修复方法
一共6个问题,逐个修改即可:
- 接口漏传参数:register方法接收了password参数,但post请求体里没带password,后端拿不到密码字段会触发逻辑异常,请求时需要把password加到请求体里。
- 错误字段匹配错误:后端返回的错误信息存在
err字段里,前端错误处理写的是取data.message,拿不到具体报错,需要把错误判断逻辑改成同时兼容data.err和data.message。 - thunk无返回值+未抛出错误:原有register thunk请求成功后没有return用户数据,请求失败也没有抛出reject,导致dispatch之后的.then拿到undefined,访问
user._id直接触发类型错误。需要修改thunk逻辑:成功时return响应数据,失败时throw错误,避免无意义的.then执行。 - reducer字段拼写不一致:初始state写的是
userinfo(i小写),成功分支写的是userInfo(I大写),字段不匹配会导致后续取状态拿不到值,需要统一字段名为userInfo。 - 跳转逻辑不合理:不要在dispatch的回调里拿数据跳转,用useSelector从redux store取userInfo和error状态,通过useEffect监听状态变化:注册成功拿到userInfo再跳转,出现错误直接弹提示。
- 500报错直接原因:MongoDB给email字段加了唯一索引,提交的邮箱
samarkan@mail.com已经在库里存在了,因为后端没做重复邮箱的前置校验,直接抛了数据库层的重复键错误,可以在后端加一层重复邮箱判断,返回更友好的提示。
修复后核心代码参考
修复后的userReducer.js
import Axios from "axios"; export const register = ({name, fname, phone, email, password}) => async (dispatch)=> { dispatch({type: "USER_REGISTER_REQUEST", payload: {name, fname, phone, email}}); try { // 补上password参数,接口路径前加/避免相对路径异常 const {data} = await Axios.post("/api/users/register",{name, fname, phone, email, password}) dispatch({type: "USER_REGISTER_SUCCESS", payload: data }) // 成功时返回用户数据 return data } catch (error) { // 兼容后端返回的err字段 const errMsg = error.response?.data?.err || error.response?.data?.message || error.message dispatch({type: "USER_REGISTER_FAIL", payload: errMsg}) // 失败时抛出错误,阻断后续.then执行 return Promise.reject(errMsg) } }; export const registerReducer = (state = {loading: false, userInfo: {}}, action) => { switch(action.type) { case "USER_REGISTER_REQUEST": return {loading: true, userInfo: {}} case "USER_REGISTER_SUCCESS": return {loading: false, userInfo: action.payload}; case "USER_REGISTER_FAIL": return {loading: false, error: action.payload, userInfo: {}} default: return state; } };
修复后的RegisterPage.js核心逻辑
// 从store取状态 const { loading, userInfo, error } = useSelector(state => state.user) const navigate = useNavigate(); // 监听状态变化处理跳转和错误提示 useEffect(() => { if(userInfo?._id) { navigate(`/profile?id=${userInfo._id}&name=${userInfo.name}&fname=${userInfo.fname}&email=${userInfo.email}`) } if(error) { window.alert(error) } }, [userInfo, error, navigate]) const submitHandler = (e) => { e.preventDefault() if(password !== confirm || !validateEmail(email)) { window.alert("两次输入密码不一致或邮箱格式错误!") return } // 提交时把password传进去,不需要在这里链式调用跳转 dispatch(register({name, fname, phone, email, password})) }
内容的提问来源于stack exchange,提问作者Borhen Kalboussi
相关产品推荐
相关产品推荐

