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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 14:45:59