返回其他页面后,注册表单仍弹出错误Toast通知问题排查
问题描述
注册失败后切换至其他页面,再返回注册表单时,即便表单已清空,错误Toast仍会再次弹出。例如注册失败后跳转至登录页面,再回到注册表单,错误Toast会立即弹出。
附代码:
import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { signupForm, resetSignupState} from '../redux/actionSignup'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; const Signup = () => { const navigate = useNavigate(); const dispatch = useDispatch(); const { isSignedUp, errors: signupErrors} = useSelector((state) => state.signup); const [formData, setFormData] = useState({ //ok username: '', email: '', password: '', confirmpassword: '', }); useEffect(() => { dispatch(resetSignupState()); }, [dispatch]); const handleChange = (e) => { //ok const { name, value } = e.target; setFormData({ ...formData, [name]: value, }); }; const validateForm = () => { //ok const newErrors = {}; if (!formData.username.trim()) { newErrors.username = 'Username is not valid'; } if (!formData.email.includes('@gmail.com')) { newErrors.email = 'Invalid email!'; } if (formData.password.length < 8) { newErrors.password = 'Password must be at least 8 characters!'; } if (formData.password !== formData.confirmpassword) { newErrors.confirmpassword = 'Password does not match!'; } return newErrors; }; const handleSubmit = (e) => { e.preventDefault(); const newErrors = validateForm(); dispatch(signupForm({ ...formData, errors: newErrors, isValid: Object.keys(newErrors).length === 0 })); }; useEffect(() => { if (isSignedUp) { toast.success('Signed Up Successfully!', { className: 'custom-toast-success', }); setFormData({ username: '', email: '', password: '', confirmpassword: '', }); dispatch(resetSignupState()); navigate('/login'); } }, [isSignedUp, dispatch, navigate]); useEffect(() => { if (signupErrors && Object.keys(signupErrors).length > 0) { toast.error('Signed Up Failed!', { className: 'custom-toast-error', }); } }, [signupErrors]); useEffect(() => { dispatch(resetSignupState()); setFormData({ username: '', email: '', password: '', confirmpassword: '', }); }, [dispatch]); return ( <div className='signup-template d-flex justify-content-center align-items-center vh-100'> <div className='signupform-container p-5 rounded'> <form onSubmit={handleSubmit}> <h1 className='text-center signup-title'>Sign Up</h1> <div className='text-start mb-2'> <label htmlFor='username' className='signup-props'>Username</label> <input type='text' placeholder='Enter Username' className='form-control signup-list color-background' style={{ border: '1.4px solid black' }} id='username' name='username' value={formData.username} onChange={handleChange} /> {signupErrors.username && <div className='error' style={{ color: 'crimson' }}>{signupErrors.username}</div>} </div> <div className='text-start mb-2'> <label htmlFor='email' className='signup-props'>Email</label> <input type='email' placeholder='Enter Email' className='form-control signup-list color-background' style={{ border: '1.4px solid black' }} id='email' name='email' value={formData.email} onChange={handleChange} /> {signupErrors.email && <div className='error' style={{ color: 'crimson' }}>{signupErrors.email}</div>} </div> <div className='text-start mb-2'> <label htmlFor='password' className='signup-props'>Password</label> <input type='password' placeholder='Enter Password' className='form-control signup-list color-background' style={{ border: '1.4px solid black' }} id='password' name='password' value={formData.password} onChange={handleChange} /> {signupErrors.password && <div className='error' style={{ color: 'crimson' }}>{signupErrors.password}</div>} </div> <div className='text-start mb-2'> <label htmlFor='confirmpassword' className='signup-props'>Confirm Password</label> <input type='password' placeholder='Confirm Password' className='form-control signup-list color-background' style={{ border: '1.4px solid black' }} id='confirmpassword' name='confirmpassword' value={formData.confirmpassword} onChange={handleChange} /> {signupErrors.confirmpassword && <div className='error' style={{ color: 'crimson' }}>{signupErrors.confirmpassword}</div>} </div> <div className='text-center mt-3 signup-buttonsz'> <button type='submit' className='signup-button' name='submit'> Submit </button> </div> </form> </div> </div> ); }; export default Signup;
问题定位
- 重复的重置逻辑:代码里有两个完全相同的
useEffect,都在调用resetSignupState()和清空表单,属于冗余代码,且可能导致状态重置时机混乱。 - Toast触发条件太宽泛:监听
signupErrors的useEffect,只要signupErrors有内容就触发Toast。当从其他页面返回时,Redux中残留的旧错误状态会被读取,此时组件还没完成状态重置,Toast就先弹出来了。 - 状态重置与Toast触发的顺序问题:组件挂载时,重置状态的
useEffect和触发Toast的useEffect执行顺序不确定,大概率是Toast先基于旧状态弹出,之后才执行状态重置。
修复方案
步骤1:移除重复的重置Effect
删掉其中一个完全重复的useEffect,只保留一个,避免冗余执行:
// 保留这个,删除另一个完全相同的useEffect useEffect(() => { dispatch(resetSignupState()); setFormData({ username: '', email: '', password: '', confirmpassword: '', }); }, [dispatch]);
步骤2:优化Toast触发逻辑,区分首次挂载与提交后错误
通过组件内状态标记是否完成首次挂载,只有挂载完成后(即用户可能提交表单后)的错误才触发Toast:
const Signup = () => { // 新增状态:标记组件是否完成首次挂载 const [isMounted, setIsMounted] = useState(false); const navigate = useNavigate(); const dispatch = useDispatch(); const { isSignedUp, errors: signupErrors} = useSelector((state) => state.signup); // ...其他原有代码 // 修改重置状态的Effect,挂载完成后标记状态 useEffect(() => { dispatch(resetSignupState()); setFormData({ username: '', email: '', password: '', confirmpassword: '', }); setIsMounted(true); }, [dispatch]); // 修改Toast触发逻辑:仅在挂载完成且有错误时弹出 useEffect(() => { if (isMounted && signupErrors && Object.keys(signupErrors).length > 0) { toast.error('Signed Up Failed!', { className: 'custom-toast-error', }); } }, [signupErrors, isMounted]); // ...其他原有代码 };
步骤3:确保Redux状态重置的完整性
检查resetSignupState对应的reducer,确保所有相关状态都被重置,比如把errors清空:
// 示例reducer代码,根据你的实际reducer调整 const initialState = { isSignedUp: false, errors: {}, // 其他状态字段... }; const signupReducer = (state = initialState, action) => { switch(action.type) { case 'RESET_SIGNUP_STATE': return { ...initialState, // 或者明确重置每个字段 isSignedUp: false, errors: {} }; // ...其他case逻辑 } };
可选优化:通过Redux状态标记提交状态(更严谨)
如果想彻底区分“用户提交产生的错误”和“旧状态残留错误”,可以在Redux的signup状态中新增hasSubmitted字段:
- 在
signupFormaction中,提交表单时将hasSubmitted设为true - 在
resetSignupStateaction中,将hasSubmitted设为false,同时清空errors - 组件中修改Toast触发逻辑:
// 从Redux中取出hasSubmitted const { isSignedUp, errors: signupErrors, hasSubmitted} = useSelector((state) => state.signup); // ...其他代码 useEffect(() => { // 仅当用户提交过且存在错误时弹出Toast if (hasSubmitted && signupErrors && Object.keys(signupErrors).length > 0) { toast.error('Signed Up Failed!', { className: 'custom-toast-error', }); // 弹出后重置状态,避免再次返回时触发 dispatch(resetSignupState()); } }, [signupErrors, hasSubmitted, dispatch]);
内容的提问来源于stack exchange,提问作者A Boy Learns to Code
相关产品推荐
相关产品推荐

