React-Bootstrap登录Modal未设置showLoginModal为false却自动消失
登录Modal点击登录后自动消失问题排查与解决
问题描述
我有一个基于React-Bootstrap的登录Modal,填写用户名和密码点击登录后,Modal没等showLoginModal设为false就自动消失了。组件代码如下:
import { Modal } from 'react-bootstrap' const AccountsMenu: React.FC = () => { const dispatch = useDispatch() const navigate = useNavigate() const accounts = useSelector((state: RootState) => state.accounts) const location = useLocation() const getCurrentPath = () => location.pathname + encodeURIComponent(location.search) useEffect(() => { dispatch(getUserInfoAction()) }, [dispatch]) const registerPage = () => { navigate(ROUTES.REGISTER_ROUTE) } const [showLoginModal, setShowLoginModal] = useState(false) const [username, setUsername] = useState<string>('') const [password, setPassword] = useState<string>('') const [usernameError, setUsernameError] = useState<string>('') const [passwordError, setPasswordError] = useState<string>('') const login = () => { // Check if username and password are not empty if (!username.trim()) { setUsernameError('Username cannot be empty.') return } else { setUsernameError('') } if (!password.trim()) { setPasswordError('Password cannot be empty.') return } else { setPasswordError('') } dispatch(actions.loginUser({ username: username, password: password })) setUsername('') setPassword('') } useEffect(() => { if (accounts.apiSuccess) { setShowLoginModal(false) } }, [accounts.apiSuccess]) if (accounts.loading) return <li>Loading ...</li> return ( <li className="nav-item dropdown"> <a className="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Account </a> <ul className="dropdown-menu"> {accounts.userInfo.is_active ? ( <li> <a href={'/b/logout?next=' + getCurrentPath()} className="dropdown-item"> Logout </a> </li> ) : ( <> <li> <a href={'/b/login?next=' + getCurrentPath()} className="dropdown-item"> Login </a> </li> <li> <button onClick={() => registerPage()} className="dropdown-item"> Register </button> </li> <li> <button onClick={() => setShowLoginModal(true)} className="dropdown-item"> Login modal </button> </li> </> )} </ul> <Modal show={showLoginModal} onHide={() => setShowLoginModal(false)} centered={true}> <div className="d-flex justify-content-center m-3"> <ErrorHandling error={accounts.error} loading={accounts.loading} /> </div> <form className="p-4"> <div className="form-outline mb-4"> <label className="form-label" htmlFor="username"> User name: </label> <input type="text" id="username" className={`form-control ${usernameError ? 'is-invalid' : ''}`} value={username} onChange={(e) => { setUsername(e.target.value) setUsernameError('') }} /> {usernameError && <div className="invalid-feedback">{usernameError}</div>} </div> <div className="form-outline mb-4"> <label className="form-label" htmlFor="password"> password: </label> <input type="password" id="password" className={`form-control ${passwordError ? 'is-invalid' : ''}`} value={password} onChange={(e) => { setPassword(e.target.value) setPasswordError('') }} /> {passwordError && <div className="invalid-feedback">{passwordError}</div>} </div> <div className="form-outline mt-5 text-center"> <button type="button" className="btn btn-primary" onClick={() => login()}> Login </button> </div> </form> </Modal> </li> ) }
已尝试的方法
- 将登录按钮类型改为
button以防止表单刷新页面; - 使用
div标签替代form标签,问题依旧; - 将登录按钮移到form标签外,问题仍未解决。
问题更新
注释login函数的不同部分后发现,是dispatch(actions.loginUser({ username: username, password: password }))这一行导致的问题,不确定如何阻止该行为或通过其他方式解决。
问题根源
loginUser action执行后触发了组件重新渲染,而你在组件顶部写的if (accounts.loading) return <li>Loading ...</li>判断——当登录请求发起时,accounts.loading变为true,整个AccountsMenu组件会被替换成Loading ...,原来的Modal自然就消失了。
解决办法
方法1:调整Loading状态的渲染位置
把Loading判断移到下拉菜单内部,不要让整个组件被替换:
// 移除顶部的if判断,改为在下拉菜单中处理loading状态 return ( <li className="nav-item dropdown"> <a className="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Account </a> <ul className="dropdown-menu"> {accounts.loading ? ( <li><span className="dropdown-item">Loading ...</span></li> ) : accounts.userInfo.is_active ? ( <li> <a href={'/b/logout?next=' + getCurrentPath()} className="dropdown-item"> Logout </a> </li> ) : ( <> <li> <a href={'/b/login?next=' + getCurrentPath()} className="dropdown-item"> Login </a> </li> <li> <button onClick={() => registerPage()} className="dropdown-item"> Register </button> </li> <li> <button onClick={() => setShowLoginModal(true)} className="dropdown-item"> Login modal </button> </li> </> )} </ul> <Modal show={showLoginModal} onHide={() => setShowLoginModal(false)} centered={true}> <div className="d-flex justify-content-center m-3"> <ErrorHandling error={accounts.error} loading={accounts.loading} /> </div> {/* 原表单代码保持不变 */} </Modal> </li> )
方法2:检查action内的路由跳转逻辑
如果loginUser action内部直接执行了路由跳转(比如调用navigate),也会导致Modal消失。这种情况需要把跳转逻辑和关闭Modal的逻辑同步,放在apiSuccess的监听Effect里:
useEffect(() => { if (accounts.apiSuccess) { setShowLoginModal(false) // 在这里统一执行登录成功后的跳转逻辑 navigate('/your-target-path') } }, [accounts.apiSuccess, navigate])
内容的提问来源于stack exchange,提问作者Vahid
相关产品推荐
相关产品推荐

