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:
div
tag instead of form
tag, still it behaved the same.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.
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.