I am trying to work on bootstrap 5 alpha's validation on my react app.
So basically the form won't submit by default if they are left blank and will show either a check or an error mark at the bottom.
What I did so far is that I of course added the node packages for bootstrap 5 on my index.js which works fine. Next, I added this script tag to my public folder via BootstrapValidation.js file:
(function () {
const forms = document.querySelectorAll('.needs-validation')
Array.from(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
And then inside my public folder's index.html I added it:
<script src="%PUBLIC_URL%/BootstrapValidation.js"></script>
When I check it on the source code I can see that it's loading it but when I added the required classes on my react component it doesn't show the errors nor prohibits it from submitting if there are blank fields:
const LoginScreen = ({ location, history }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dispatch = useDispatch()
const userLogin = useSelector(state => state.userLogin)
const { loading, error, userInfo } = userLogin
const redirect = location.search ? location.search.split('=')[1] : '/'
useEffect(() => {
if(userInfo){
history.push(redirect)
}
}, [history, userInfo, redirect])
const submitHandler = (e) => {
e.preventDefault()
// DISPATCH LOGIN
dispatch(login(email, password))
}
return (
<>
<h1>Sign In</h1>
{ error && <Message variant='danger'>{error} </Message>}
{ loading && <Loader /> }
<form onSubmit={submitHandler} className="needs-validation" novalidate>
<div className="form-group">
<label for="email">Email address</label>
<input type="email" name="email" className="form-control" id="email" placeholder="Enter email" onChange={(e) => setEmail(e.target.value)} required/>
<div className="valid-feedback">Looks good!</div>
<div class="invalid-feedback"> Please supply a name.</div>
</div>
<div className="form-group">
<label for="password">Password</label>
<input type="password" name="password" className="form-control" id="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} required/>
</div>
<button type="submit" className="btn btn-primary">Login</button>
</form>
<p>New User? <Link to={redirect ? `/register?redirect=${redirect}` : '/register' }>Register</Link></p>
</>
)
}
export default LoginScreen
Any idea what's causing this error or how can I properly execute this script so it will run as expected like on the documentation of bootstrap 5?
I think the main problem is that the form that is rendered by your component, has not rendered before the function in BootstrapValidation.js has run, meaning the classes that display the validation styles you want are not added to html.
Also you need to adjust the casing of properties like novalidate to noValidate and class to className, for to htmlFor, etc...
After you've done this you could move the logic of adding class names to run inside a useEffect hook like this:
useEffect(() => {
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.querySelectorAll(".needs-validation");
// Loop over them and prevent submission
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener(
"submit",
function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
},
false
);
});
}, []);
Related
I'm creating a simple react application with 3 user roles. On the login page localhost:3000/login I have fields for username and password. Once those details are entered and the login button is clicked, the data is sent to the backend (running node js) and that data is used to query a MySql database. If the entered data matches a user in the database, the userId, name, password, and role is sent to the backend. This data is then sent to the front end. I can read the retrieved data from the front end and up to this point it works fine. However, when I'm trying to redirect a user according to the role, say the role is doctor and I want to redirect the user to localhost:3000/doctor , it goes to localhost:3000/doctor momentarily and switches to localhost:3000/login?. Shown below is the code for the login component.
import { useState } from "react";
import Axios from 'axios';
import { useNavigate } from 'react-router-dom'
import './login.css';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
let navigate = useNavigate()
const handleLogin = () => {
Axios.post("http://localhost:3001/login",
{
email: email,
password: password,
},
{
headers: {
'Content-Type': 'application/json'
}
}
)
.then((response) => {
console.log('response 1', response.data[0]['role'])
if (response.data[0]['role'] === 'doctor') {
navigate('/doctor');
}
});
};
return (
<div>
<form>
<h3>Electronic Prescription System</h3>
<h3>Login</h3>
<label>Email Address</label>
<input
className="inputs"
type="text"
placeholder="email"
onChange={(e) => {
setEmail(e.target.value)
}}
/>
<label>Password</label>
<input
className="inputs"
type="password"
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={handleLogin}>Log in</button>
</form>
</div>
)
};
export default Login;
If I remove all the code inside the handleLogin function and just have navigate('/doctor'); it redirects properly.
The routes are inside the Main component as shown below.
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Login from "./pages/Login/Login";
import Doctor from "./pages/Doctor/Doctor";
import Patient from "./pages/Patient/Patient";
import Pharmacy from "./pages/Pharmacy/Pharmacy";
const Main = () => {
return (
<Routes>
<Route path="login" element={<Login />} />
<Route path="doctor" element={<Doctor />} />
<Route path="patient" element={<Patient />} />
<Route path="pharmacy" element={<Pharmacy />} />
</Routes>
);
}
export default Main;
The Doctor Component:
import { HeaderPanel } from '../../components/headerPanel/headerPanel'
import { PrescribePanel } from '../../components/prescribePanel/prescribePanel'
import { PrescriptionsList } from '../../components/prescriptionsList/prescriptionsList'
import './styles.css';
export const Doctor = () => {
return (
<>
<HeaderPanel />
<div className='wrapper'>
<PrescribePanel />
<PrescriptionsList />
</div>
</>
);
}
export default Doctor
I'm using react-router-dom version 6.6.1 and the react version is 18.2.0.
Tried using a useEffect hook to capture the role changing and redirecting, but id did not work either.
What I suspect is happening here is that the "log in" button is submitting the form which takes the default form action and reloads the page, the current route path being "/login". button elements have a type="submit" attribute value by default.
To resolve I'd suggest attaching handleLogin to the form element's onSubmit event handler and calling preventDefault on the onSubmit event object to prevent submitting the form and prevent reloading the page. This should allow the authentication logic to complete as expected.
Try to get yourself in the habit of being specific with the button element's type attribute.
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();
const handleLogin = (e) => { // <-- onSubmit event object
e.preventDefault(); // <-- don't take form action
Axios.post(
"http://localhost:3001/login",
{ email, password },
{
headers: {
'Content-Type': 'application/json'
}
}
)
.then((response) => {
if (response.data[0]['role'] === 'doctor') {
navigate('/doctor');
}
});
};
return (
<div>
<form onSubmit={handleLogin}> // <-- form onSubmit event handler
<h3>Electronic Prescription System</h3>
<h3>Login</h3>
<label>
Email Address
<input
className="inputs"
type="text"
placeholder="email"
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password
<input
className="inputs"
type="password"
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<button type="submit"> // <-- be specific with button type
Log in
</button>
</form>
</div>
);
};
export default Login;
I have a project in which I have to add a registration form and I want to to validate that the password and confirm fields are equal without clicking the register button.
If password and confirm password field will not match, then I also want to put an error message at side of confirm password field and disable registration button.
I had these for handle password and username
const LoginForm = ({ register = false }) => {
const [isLoading, setLoading] = React.useState(false)
const [errors, setErrors] = React.useState([])
const [username, setUsername] = React.useState('')
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const handleUsernameChange = React.useCallback(
(e) => setUsername(e.target.value),
[setUsername]
)
const handleEmailChange = React.useCallback(
(e) => setEmail(e.target.value),
[]
)
const handlePasswordChange = React.useCallback(
(e) => setPassword(e.target.value),
[]
)
and Got handle submit
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
try {
let data, status
if (register) {
;({ data, status } = await UserAPI.register(username, email, password))
} else {
;({ data, status } = await UserAPI.login(email, password))
}
if (status !== 200 && data?.errors) {
setErrors(data.errors)
}
if (data?.user) {
// We fetch from /profiles/:username again because the return from /users/login above
// does not contain the image placeholder.
const { data: profileData, status: profileStatus } = await UserAPI.get(
data.user.username
)
if (profileStatus !== 200) {
setErrors(profileData.errors)
}
data.user.effectiveImage = profileData.profile.image
window.localStorage.setItem('user', JSON.stringify(data.user))
setCookie('auth', data.user.token)
mutate('user', data.user)
Router.push('/')
}
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
I want to add new confirm password field to this
<form onSubmit={handleSubmit}>
<fieldset>
{register && (
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Username"
value={username}
onChange={handleUsernameChange}
/>
</fieldset>
)}
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="email"
placeholder="Email"
value={email}
onChange={handleEmailChange}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</fieldset>
<button
className="btn btn-lg btn-primary pull-xs-right"
type="submit"
disabled={isLoading}
>
{`${register ? 'Sign up' : 'Sign in'}`}
</button>
</fieldset>
</form>
What is the most elegant way to add confirm password validation?
The elegant way to create a form, in general, is using a form library. The form libraries will make your work easier, more elegant, and more developable. Most of them have a technique to use a function or a scheme as a validator that will help you certify your password.
The most popular form libraries currently are Formik and React Hook Form and if you are using Redux you can use Redux Form.
In case you want to continue your current way of handling the form the best possible name for the second field is passowrdConfirmation is the best name in my Idea. Furthermore, you can create a validation function that you process before every field change(using a useEffect hook) or before submitting(using onSubmit event on form element).
You can use "useEffect" hook to listen to password and confirm password inputs.
Here is a simple example: https://codesandbox.io/s/solitary-brook-tw15c
I'm learning Node and React and now know how to integrate them and am working on making registration and login using Node and React. I'm going step by step, so currently I'm trying to at least get the inputs and put them into state as an array, and then after I get that I will go to hashing the password, sending the data to Node and the database, et cetera.
At the moment however, I'm a little bit stuck here. I'm trying to enter the username and password into my "details" state and then render it on the screen (or console log it, or whatever), but when I do it it shows up very quickly and then disappears. Why is the details state reverting to an empty array? How do I fix it? I did some research on here but couldn't figure it out.
import { useState } from 'react';
function App() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [details, setDetails] = useState([]);
const readUsername = (e) => {
setUsername(e.target.value);
}
const readPassword = (e) => {
setPassword(e.target.value);
}
const updateDetails = () => {
setDetails([username, password]);
}
return (
<div>
<h1>Register</h1>
<form>
<label htmlFor="username" name="username">Username: </label>
<input htmlFor="username" name="username" onChange={readUsername} />
<br/>
<label htmlFor="password" name="password">Password: </label>
<input htmlFor="password" name="password" type="password" onChange={readPassword} />
<br/>
<button onClick={updateDetails}>Submit</button>
</form>
<h1>{details}</h1>
</div>
);
}
export default App;
with the onChange handler on your input's, it is considered a "controlled" component. you also need to assign the value prop.
<input onChange={readUsername} value={username} />
Forms in React have the default behaviour as in HTML: refreshing the page upon submission.
React state only exists during the component's life. When you refresh the page, the component is unmounted, and the state is lost.
To prevent the page refresh, use a function to handle the form submission, and prevent the default behaviour.
const handleSubmit = (e) => {
e.preventDefault();
}
return (
...
<form onSubmit={handleSubmit}>
...
</form>
...
);
}
View demo on codesandbox
Further reading:
Forms in React
Preventing default behaviour of events
You want to create a controlled input so you should pass the value={password} attribute
like this:
<input htmlFor="username" name="username" onChange={readUsername} value={username} />
Also, I'd change how you handle to form. Change the button to type="submit"
like this
<button type="submit">Submit</button>
And then handle the submit from the <form>
like this:
<form onSubmit={(event) => updateDetails(event)}
And then on the function, you can use the submit event like this for example
const updateDetails = (e) => {
event.preventDefault()
...rest of logic
}
I want to submit my login form, but I need to press twice times to accomplish login form, I don't know what happen.
I'm using styled components, this Button Form is an input tag
Also I'm using firebase-hooks
I just want to click one time and submit
Here my code
export default function LoginForm() {
// submit
const handleLogin = (e) => {
e.preventDefault();
signInWithEmailAndPassword(email, password);
if (user) {
setTimeout(() => {
history.push(`${ROUTES.HOME}`);
}, 1000);
}
};
if (loading) {
return <Loading />;
}
return (
<FormContainer onSubmit={(e) => handleLogin(e)}>
<LogoContainer>
<img
src="https://firebasestorage.googleapis.com/v0/b/auth-c068d.appspot.com/o/instagram%2Finstagram-signup.png?alt=media&token=cdafffb1-3034-474d-be96-d507b5e416c6"
alt="instagram-logo"
/>
</LogoContainer>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<ButtonForm type="submit" name="btn-login" value="Log In" />
<p>
Don't have an account? <Link to="/signup">Sign Up</Link>
</p>
{error && <Error errorMessage={error.message} />}
</FormContainer>
);
}
It looks like you will need to use a useEffect hook here. Your handleLogin function only runs once on each click. The first time you submit, user is undefined as expected. It works when you click it the second time because user is truthy.
Try removing this from the handleLogin function.
if (user) {
setTimeout(() => {
history.push(`${ROUTES.HOME}`);
}, 1000);
}
Then add a useEffect hook.
useEffect(() => {
if (user) {
setTimeout(() => {
history.push(`${ROUTES.HOME}`);
}, 1000);
}}, [user])
This effect will run on component mount, and after each time your user state is changed. (This is assuming your using react state for user, as I can't see where user is coming from)
change the type of button from type="submit" to type="button" and handle click using onCkick={() => clickHandler()}
I have a form inside which i am showing edit,save and cancel button logically, so initially edit button is visible and all inputs are disabled, and when I click on edit I am making my save and cancel button visible and edit not visible.
So after filling some data when user click on save I am checking the validation like required fields, so if error then user can see.
After then on click of edit if user do not want to save then I am filling the data in site for to the initial values, but if there is error on click of save and I am clicking cancel still the error is there it is not going away,
What I am doing wrong
I think on click when i am filling my formdata to initial value.
if above point is correct then why error is still visible
my code
import React, { useState, useEffect } from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
// mock for useQuery
const useQuery = query => {
const [loading, setIsLoading] = useState(true);
const [data, setData] = useState({});
useEffect(() => {
setTimeout(() => {
setIsLoading(false);
setData({ firstname: "steve", lastname: "smith" });
}, 1000);
}, []);
return { loading, data };
};
export default function App() {
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(true);
const [editBtn, seteditBtn] = useState(true);
const [initialData, setinitialData] = useState({});
const { loading, data } = useQuery("some qraphql query here"); // getting data from graphql
const [formData, setFormData] = useState({});
useEffect(() => {
setFormData(data);
setinitialData(data);
}, [data]);
const edit = () => {
setdisabled(false);
seteditBtn(false);
};
const cancel = () => {
setFormData(initialData);
setdisabled(true);
seteditBtn(true);
};
const onSubmit = () => {
console.log(formData);
};
const handleChange = e => {
const name = e.target.name;
const value = e.target.value;
console.log(name, value);
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<div className="container-fluid">
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<div>
<button className="btn white_color_btn" type="submit">
Save
</button>
<button
className="btn white_color_btn"
type="submit"
onClick={cancel}
>
Cancel
</button>
</div>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="firstname"
name="firstname"
onChange={handleChange}
value={formData.firstname}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
</div>
);
}
To check the issue follow this points
click on edit -> empty the field -> then click save -> it will throw error -> then click cancel.
on cancel click I want error should go away
Working code codesandbox
The errors are present because they're managed by useForm. The hook exposes a function reset that should fix your problem. Here is an example that leverage the function.
const { register, handleSubmit, reset, errors } = useForm();
// ...
const cancel = () => {
setFormData(initialData);
setdisabled(true);
seteditBtn(true);
reset();
};
A simple pattern is you set errors in state and clear the values of errors object to null or empty upon clicking cancel Button or when a valid input is typed. Here you can initialize errors and reset on cancel button's click. So you should update errors every time input value is changed or cancel button is clicked.