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
Related
This question already has answers here:
Firebase Prevent Creating Account Before Email Verification
(1 answer)
Firebase - Freeze an account until email is verified
(1 answer)
Firebase email verification not working as expected, without verification the user is able to login
(4 answers)
Closed 8 months ago.
I'm trying to create a Firebase authentication system in my React web app. I know how to register an account and I know how to send a verification email out. Upon registration it sends the verification email which is fine, but the account gets added to the Firebase console database before the user has even verified it. I want it to be such that only verified accounts are added to the database.
Here is my code:
import React, { useState, useEffect, useRef } from 'react'
import './join.css'
import { Link, useNavigate } from 'react-router-dom'
import { auth } from '../firebase'
import { createUserWithEmailAndPassword, onAuthStateChanged, sendEmailVerification } from "firebase/auth"
export default function Join() {
const [registerEmail, setRegisterEmail] = useState('');
const [registerPassword, setRegisterPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [user, setUser] = useState({});
const verificationMessage = useRef();
useEffect(() => {
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
}, [])
const navigate = useNavigate();
const register = async () => {
try {
const user = await createUserWithEmailAndPassword(auth, registerEmail, registerPassword);
console.log(user);
} catch (error) {
console.log(error.message);
}
}
const handleSubmit = event => {
if (registerPassword === confirmPassword) {
sendEmailVerification(auth.currentUser);
verificationMessage.current.style.display = 'block';
register();
event.preventDefault();
}
else {
alert('Passwords do not match, please try again!');
event.preventDefault();
}
}
return (
<>
<div className="signup-div">
<h2>Sign Up</h2>
<form onSubmit={handleSubmit}>
<fieldset>
<label className='labels'>Email:</label><br />
<input placeholder='Please enter your email' type="email" value={registerEmail} onChange={e => setRegisterEmail(e.target.value)} required />
</fieldset>
<fieldset>
<label className='labels'>Set Password:</label><br />
<input placeholder='Create password' type="password" value={registerPassword} onChange={e => setRegisterPassword(e.target.value)} required />
</fieldset>
<fieldset>
<label className='labels'>Re-type password to confirm:</label><br />
<input placeholder="Confirm password" type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} required />
</fieldset>
<button type="submit">Sign Up</button>
</form>
<div>
Already have an account? <Link to="/login">Log In</Link>
</div>
</div>
<div className='verification-message' ref={verificationMessage}>Welcome aboard! Please check your inbox and click the link in our verification email to verify your account. This could take upto a few minutes to arrive.</div>
</>
)
}
I have a problem in the input validation. The validation works when I submit and a error message appears, but when I press the first key on the keyboard nothing appears in the textarea and the error message disappears; after that, I can write normally. Its an inconvenience and I don't know why its happening. I am using the TextArea from Material UI. The code snippet of a login form is below.
const schema = yup.object().shape({
username: yup.string().matches(/^[a-z0-9]+$/, 'Must be all lower-case letters.').required(),
password: yup.string().required(),
})
const Login = props => {
const [formValues, setFormValues] = React.useState({
username: "",
password: ""
});
const { register,errors, handleSubmit } = useForm({
resolver: yupResolver(schema),
mode: 'onSubmit',
});
const onSubmit = async (data, e) => {
e.preventDefault()
const isValid = await schema.isValid(data)
if(isValid){
console.log(data);
}
}
return (
<Container component="main" maxWidth="xs">
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
autoFocus
required
fullWidth
id="username"
label="Username"
name="username"
value={formValues.username}
inputRef={register}
helperText = {errors.username?.message}
/>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={formValues.password}
inputRef={register}
helperText = {errors.password?.message}
/>
<Button
type="submit"
fullWidth
className={classes.submit}
>
Login
</Button>
</form>
</div>
</Container>
);
}
I worked around this (in React Native) by using reValidateMode:"onBlur" in the useForm() options.
This way it doesn't retry the validation until the user leaves the input.
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
);
});
}, []);
I am trying to build a registration form, I have built the login form and it works perfectly, but the registration form is quite off.
First I used the react-dev tools to inspect what is going on and I realized that each input value coming from the registration form happens to be in an array.
I went back to the login form to inspect and saw that the value of the input fields is in the right format (strings). But the value from each specific input field in the registration form is in an array.
What could I be doing wrong?
I tried to replicate what I did in the login form in the registration form, but it is still coming in an array. Also, should hooks be kept in its own separate file?
This is what I see from the react-dev tools for the registration form.
Here is a code snippet of what I did.
const Signup = (props) => {
const authContext = useContext(AuthContext);
const { register, clearErrors, isAuthenticated, error } = authContext;
const [loadBtn, updateLoadBtn] = useState(false);
const [user, setUser] = useState({
firstName: "",
lastName: "",
email: "",
password: "",
username: "",
phoneNumber: "",
});
useEffect(() => {
if (isAuthenticated) {
successMessage();
props.history.push("/dashboard");
}
if (error) {
missingValue(error);
updateLoadBtn(false);
clearErrors();
}
//eslint-disable-next-line
}, [isAuthenticated, error, props.history]);
const { firstName, lastName, email, password, username, phoneNumber } = user;
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
const onSubmit = (e) => {
e.preventDefault();
updateLoadBtn(true);
if (
!firstName ||
!lastName ||
!email ||
!password ||
!username ||
!phoneNumber
) {
missingValue("Please enter all fields");
updateLoadBtn(false);
clearErrors();
} else {
register({
firstName,
lastName,
email,
password,
username,
phoneNumber,
});
}
};
return (
<Fragment>
<ToastContainer />
<RegContainer className="container-fluid py-4">
<RegInfo />
<RegColumn
onChange={onChange}
onSubmit={onSubmit}
firstName={firstName}
lastName={lastName}
email={email}
password={password}
phoneNumber={phoneNumber}
loadBtn={loadBtn}
/>
</RegContainer>
</Fragment>
);
}
That is the file responsible for handling the registration.
Here is the custom component
const RegColumn = ({
firstName,
onSubmit,
onChange,
lastName,
username,
password,
phoneNumber,
email,
loadBtn,
}) => {
const bodyStyle = document.querySelector("body").style;
bodyStyle.backgroundImage = "linear-gradient(to bottom, #F6F6F2, #C2EDCE)";
bodyStyle.backgroundRepeat = "no-repeat";
bodyStyle.overflow = "hidden";
bodyStyle.height = "100%";
bodyStyle.fontFamily = "Rubik, sans-serif";
return (
<Fragment>
<div id="reg-column">
<h3 style={{ color: "#388087" }}>REGISTRATION</h3>
<Form divid="form-row1-label" onSubmit={onSubmit}>
<LabelContainer id="form-row1-label">
<LabelContainer id="firstNameLabel">
<LabelInfo labelfor="firstName" labeltitle="First Name" />
</LabelContainer>
<LabelContainer id="lastNameLabel">
<LabelInfo labelfor="lastName" labeltitle="Last Name" />
</LabelContainer>
</LabelContainer>
<InputContainer id="form-row1-input">
<InputContainer id="firstNameInput">
<Input
type="text"
name="firstName"
value={firstName}
id="firstName"
onChange={onChange}
/>
</InputContainer>
<InputContainer id="lastNameInput">
<Input
type="text"
onChange={onChange}
name="lastName"
value={lastName}
id="lastName"
/>
</InputContainer>
// ...
Thank you.
Within your Signup form, you have this...
// ...
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
What's happening above?
The onChange() is responsible for updating the values to be submitted.
Within setUser, you are passing a value as a literal array using [e.target.value]
Solution:
Remove the literal array [] and pass value as it's received i.e. e.target.value
The left hand side, e.target.name, is fine since you are actually using a computed property name.
You can also read more about handling forms in react.
I'm creating a input form for an e-mail and i have a delayed onChange on it to not call the api too many times.
Here's my code:
const InformationCollection = (props) => {
const [email, setEmail] = useState()
const [collectedEmail, setCollectedEmail] = useState(1)
useEffect(() => {
let timeout = setTimeout(() => {
setCollectedEmail(email)
console.log(collectedEmail)
}, 500)
return () => {
clearTimeout(timeout)
}
}, [email])
return (
<div className="form-group">
<label htmlFor="inputmail">Email address</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)
}
export default InformationCollection
On this line if i type "1" console.log(e.target.value + "this is what is set" + email), e.target.value is 1, but email is undefined.
On the next character "12", e.target.value is 12 but email is 1
Can anyone help with this?
UPDATE:
The solution is to have 2 useEffectHooks. One for the value in the form email and one for the delayed value collectedEmail
Second solution is to do fetch inside the first useEffect hook
const InformationCollection = (props) => {
const [email, setEmail] = useState()
const [collectedEmail, setCollectedEmail] = useState()
useEffect(() => {
let timeout = setTimeout(() => {
//fetch directly here
setCollectedEmail(email)
console.log(collectedEmail)
}, 500)
return () => {
clearTimeout(timeout)
}
}, [email])
useEffect(() => {
//fetch() here
console.log(collectedEmail) //right value
}, [collectedEmail])
return (
<div className="form-group">
<label htmlFor="inputmail">Email address</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)
}
export default InformationCollection
state is updated asynchronously, that's why email is undefined for the first time when you try to log it after updating the state.
You can log the email inside useEffect hook which will be called after email has changed.
On the next character "12", e.target.value is 12 but email is 1
email is 1 because when onChange event fired for the first time, email was undefined but when onChange event fires for the second time, email had already been updated asynchronously to 1
Isn't this expected behaviour? email is always the value before the change inside the onChange handler. Because the re-render hasn't happened yet.
To see the value rendered do this:
return (
<div className="form-group">
<label htmlFor="inputmail">Email address: { email }</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)