Search code examples
javascriptreactjsreduxreact-bootstrap

react-bootstrap modal disappears without setting showModal to false


I have a login Modal, from react-bootstrap, that when I fill the username and password and click on login, it disappears without waiting to showLoginModal to be set to false. This is the component:

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>
    )
}

I have tried the following so far:

  1. I have changed the login button type to 'button' instead of 'submit' to prevent the form from refreshing the page.
  2. I tried using div tag instead of form tag, still it behaved the same.
  3. I tried moving the login button outside the form, but still, the same behavior.

I searched for similar questions, but none addressed this issue.

UPDATE ON QUESTION:

I tried to comment out different parts of the login, and the part that is causing this is the dispatch(actions.loginUser({ username: username, password: password })) part. I am not sure how can I prevent this. Or if I can not, how can I solve this any other way.


Solution

  • THE SOLUTION I HAVE FOUND:

    So, as I have guessed before, the problem was from this part: dispatch(actions.loginUser({ username: username, password: password })). In other words, when the redux store got changed, it caused the page to re-render. But if we want to put it more precisely, This was the part that was causing the page to re-render:

     if (accounts.loading) return <li>Loading ...</li>
    

    So before, our redux store for account type was like this:

    export interface AccountsStoreType {
        error: string | { [key: string]: string } | null
        loading: boolean
        userInfo: UserInfoType
        registerSuccess: boolean | null
    }
    
    export interface UserInfoType {
        username?: string
        first_name?: string
        last_name?: string
        email?: string
        is_active: boolean
        is_staff: boolean | null
    }
    

    We had one loading and one error key for all the stuff related to the user, like logging in, logging out, registering, and getting user info.

    So, I tried separating the store:

    
    export interface LoginType {
        loading: boolean
        success: boolean | null
        error: string | { [key: string]: string } | null
    }
    export interface RegisterType {
        loading: boolean
        success: boolean | null
        error: string | { [key: string]: string } | null
    }
    export interface LogoutType {
        loading: boolean
        success: boolean | null
        error: string | { [key: string]: string } | null
    }
    export interface UserInfoType {
        error: string | { [key: string]: string } | null
        loading: boolean
        username?: string
        first_name?: string
        last_name?: string
        email?: string
        is_active: boolean
        is_staff: boolean | null
        about_me?: string
        user_score?: number
        user_contributions?: UserContributionType[]
    }
    export interface AccountsStoreType {
        login: LoginType
        register: RegisterType
        logout: LogoutType
        userInfo: UserInfoType
    }
    

    After separating the types, that specific line that I pointed out in the beginning of this answer, got converted to this:

        if (accounts.userInfo.loading) return <li>Loading ...</li>
    

    As you see, now it gets the loading from it's own related store, and registering does not have any affect on rendering this part.