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.
Related
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I'm trying to make a form with React but having some issues with the password and confirm password field.
What I want is basically to have a user type a password and once they confirm, it should display in real time whether the password matches the confirm password field, or if the passwords do not match.
const handlePasswordChange = (event) => {
const { name, value } = event.target;
setCustomerData(prev => ({
...prev,
[name]: value
}));
// setPasswordError displays the error on the form to the user
if(customer.password === customer.confirmPassword) {
setPasswordError('passwords match')
} else if(customer.password !== customer.confirmPassword) {
setPasswordError('Passwords dont match')
} else {
setPasswordError('');
}
console.log("password: " + customer.password);
console.log("Confirm password: " + customer.confirmPassword);
}
State Object
const [customer, setCustomerData] = useState({
firstName: "",
lastName: "",
emailAddress: "",
mobileNumber: "",
dateOfBirth: new Date(),
password: "",
confirmPassword: ""
});
// Displays error using spring boot on the backend
----------
const [firstNameError, setFirstNameError] = useState("");
const [lastNameError, setLastNameError] = useState("");
const [emailAddressError, setEmailAddressError] = useState("");
const [mobileNumberError, setMobileNumberError] = useState("");
const [dateOfBirthError, setDateOfBirthError] = useState("");
const [passwordError, setPasswordError] = useState("");
Form password and confirm password fields
{/*Password*/}
<div className="form-group">
<label htmlFor="password" className="register-form-labels">Password</label>
{passwordError ? <span className="field-validation-styling">{passwordError}</span> : ''}
<input type={passwordShown ? "text" : "password"}
onFocus={(e) => setPasswordError('')}
className="shadow-none form-control register-form-input-field"
name="password"
placeholder="Enter Password"
value={customer.password}
onChange={handlePasswordChange}
/>
</div>
{/*Confirm Password*/}
<div className="form-group">
<label htmlFor="confirmPassword" className="register-form-labels">Confirm Password</label>
<input type={passwordShown ? "text" : "password"}
minLength="8"
className="shadow-none form-control register-form-input-field"
name="confirmPassword"
placeholder="Confirm Password"
value={customer.confirmPassword}
onChange={handlePasswordChange} />
</div>
But when i type in my password, the input is a character behind. This is what i mean
So useState doesn't update immediately, you need something to listen to the changes and update afterwards.
As you are calling handlePasswordChange on change, and then in the same function checking for equality, the customer state is still the "old" state. It wont become the "new" state until reload.
The use of useEffect would be fine here, listening for changes in the customer object, and then acting upon them;
// Function to set passwords
const handlePasswordChange = (event) => {
const { name, value } = event.target;
setCustomerData((prev) => ({
...prev,
[name]: value
}));
// setPasswordError displays the error on the form to the user
console.log("password: " + customer.password);
console.log("Confirm password: " + customer.confirmPassword);
};
//useEffect with a dependency of customer
useEffect(
(_) => {
checkPassword(customer);
},
[customer]
);
// separate check password function
const checkPassword = (customer) => {
if (customer.password === customer.confirmPassword) {
setPasswordError("passwords match");
} else if (customer.password !== customer.confirmPassword) {
setPasswordError("Passwords dont match");
} else {
setPasswordError("");
}
};
I am using react-hooks and yup for my contact form. I have added my yup schema as expected and error boundaries but they don't trigger when I try to submit it with empty fields. The empty form goes straight to the database which I don't want. Why is it not triggering?
My contact form code is as follows:
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "#hookform/resolvers/yup";
import { ErrorMessage } from "#hookform/error-message";
const schema = yup.object().shape({
fullName: yup.string().required("Please enter your full name"),
email: yup.string().required("Please enter your email"),
select: yup
.string()
.oneOf(["webSolutions", "mobileSolutions", "devOps", "research", "uiux"])
.required("Please choose one of our services"),
message: yup.string().required("Message can not be left blank"),
});
const Contact = () => {
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [selecttype , setSelectType ] = useState([]);
const {
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const handleSubmit = (e) => {
e.preventDefault();
db.collection("contacts")
.add({
fullName: fullName,
email: email,
selecttype : selecttype ,
message: message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
setFullName("");
setEmail("");
setMessage("");
setSelectType("");
};
return (
<>
<Container>
<FormBg>
<ImageBg>
<Img src={Image} />
</ImageBg>
</FormBg>
<FormWrap>
<FormContent>
<Form onSubmit={handleSubmit}>
<Icon to="/">
<img src={dLogo} />
</Icon>
<FormH1>Fill in your request details below</FormH1>
<FormLabel value="fullName">
Full Name <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="text"
name="fullName"
onChange={(e) => setFullName(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="fullName" />
</ErrorTag>
<FormLabel value="email">
Email <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="email"
name="email"
placeholder="example#email.com"
onChange={(e) => setEmail(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="email" />
</ErrorTag>
<FormLabel value="services">
What would you wants to do for you?
<RequiredTag>*</RequiredTag>
</FormLabel>
<select
onChange={(e) => {
const selectedOption = e.target.value;
setSelectType (selectedOption);
console.log(selectedOption);
}}
>
<option>Select ...</option>
<option value="webSolutions">Web Solutions</option>
<option value="mobileSolutions">Mobile Solutions</option>
<option value="devOps">DevOps Solutions</option>
<option value="research">Research Assistance</option>
<option value="uiux">UI/UX Design</option>
</select>
<ErrorTag>
<ErrorMessage errors={errors} name="select" />
</ErrorTag>
<FormLabel value="message">
Message <RequiredTag>*</RequiredTag>
</FormLabel>
<textarea
name="message"
placeholder="Tell us more about your request like bugdets, questions and more"
onChange={(e) => setMessage(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="message" />
</ErrorTag>
<FormButton type="submit">Submit Request</FormButton>
<Text>We respond within 48 hours😉</Text>
</Form>
</FormContent>
</FormWrap>
</Container>
</>
);
};
export default Contact;```
You can also set .required() on your yup.object().shape({...}) which will make sure that the object will not be empty when validating.
It will look like this:
const schema = yup.object().shape({
// Your shape fields
}).required(); // -> here is where you add it
Edit
You are also not using React Hook Form's submit wrapper, this is probably why the form gets submit even though there are errors.
const { handleSubmit } = useForm();
const yourSubmitHandler = () => {
// ...
};
return (
<Form onSubmit={handleSubmit(yourSubmitHandler)}>
{/* Rest of the form */}
</Form>
);
I found the answer. Since I was using react-hooks useForm and yup to validate my form, I had to use the data captured in the form by passing it to the onSubmit function to my firebase db like this:
const onSubmit = (data) => {
db.collection("contacts")
.add({
fullName: data.fullName,
email: data.email,
select: data.select,
select2: data.select2,
message: data.message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
};
This works perfectly and I hope it does for anyone else who might be using react-hook useForm and yup for validation.
For example form with only email field:
const RegistrationForm = (props) => {
const { values, touched, errors, handleChange, handleBlur, handleSubmit, status } = props;
const emailFieldHelp = () => {
const { touched, errors, status, setStatus } = props;
console.log('status: ', status);
if (touched.email && errors.email) {
return errors.email;
}
if (status && !status.isUserAdded) {
setStatus({"isUserAdded": false});
return "User already exist";
}
return null;
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
help={emailFieldHelp()}
validateStatus={setFieldValidateStatus('email')}
label="E-mail"
name="email"
hasFeedback={touched.email && values.email !== ''}
>
<Input
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
};
const RegFormView = withFormik({
validationSchema,
mapPropsToValues: () => ({
email: ''
}),
handleSubmit: async (values, { setErrors, setSubmitting, setStatus }) => {
await userService.createUser('/signup/', values)
.then(response => {
setStatus('');
const status = (response.isAdded)
? {isUserAdded: true}
: {isUserAdded: false};
setStatus(status);
setSubmitting(false);
}, (error) => {
setErrors(error);
});
},
})(RegistrationForm);
When I send form and validate it on sever side, I return 'isAdded' = false or true. Than I set status to {isUserAdded: true} or false if user not added. and I absolutely have no idea how to show this message in form under email field and keep form working. Now I can show message but than I cant send form second time becouse of status already set to {isUserAdded: true}. Do I need somehow to change status when user change email field? but I can't do it becouse of the formik.
here onChange={handleChange} I can pass only handleChange and can't run my function or I can run my function like this onChange={myFunc()} but than its seems impossible to pass handleChange to Formik. (I dont need call it like this handleChange(e) its not work! i need somehow pass it to Formik) I'm total stuck. If you have knowledge in react plz help.
And if you know some examples how to show server side validation messages in react form, links to this examples might be helpful. ty.
If the mutation is successful, I am trying to setAdded to true in the .then of ```submitForm()``. If this is true, I want to show a message from the SuccessfulMessage(). However, when I log the value of added, I keep seeing false.
Since addedis not changed to true. I am unable to see any message when mutation is successful. Why doesn't it change?
export default function AddUserPage() {
const [state, setState] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
phoneNumber:'',
loggedIn: false,
});
const [added, setAdded] = useState(false);
function SuccessMessage(){
if (added)
{
console.log('User Added');
return (
<Typography>
User Added
</Typography>)
}
}
useEffect(() => {
if(added){
SuccessMessage();
}
},[] );
function submitForm(AddUserMutation: any) {
const { firstName, lastName, email, password, phoneNumber } = state;
if (firstName && lastName && email && password && phoneNumber) {
AddUserMutation({
variables: {
firstName: firstName,
lastName: lastName,
email: email,
password: password,
phoneNumber: phoneNumber,
},
}).then(({ data }: any) => {
setAdded(true);
console.log('doing', added);
console.log('ID: ', data.createUser.id);
console.log('doing', added);
})
.catch(console.log)
}
}
return (
<Mutation mutation={AddUserMutation}>
{(AddUserMutation: any) => (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
initialValues={{ firstName: '', lastName: '', email: '', password: '', phoneNumber: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { firstName, lastName, email, password, phoneNumber },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<div className='main-content'>
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(AddUserMutation);SuccessMessage()}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="firstName"
name="firstName"
helperText={touched.firstName ? errors.firstName : ""}
error={touched.firstName && Boolean(errors.firstName)}
label="First Name"
value={firstName}
onChange={change.bind(null, "firstName")}
/>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<Button
type="submit"
disabled={!isValid || !email || !password}
>
Add User</Button>
</div>
</form>
</div>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
Your console.log() directly after calling setAdded will not show true as state updates are async an will only be visible on the next render. Also your SuccessMessage will never be triggered because you did not provide any dependencies for your useEffect(). This means it will only ever be called after mount
You need to add added to the dependency list:
useEffect(() => {
if(added){
SuccessMessage();
}
},[added]);
But actually I don't see any reason to trigger it in a useEffect anyways. Why not just call it in the mutation handler?
Also if you are already using hooks you can use useMutation.
Also you can't return JSX from a handler. It will not do anything. How should react even know where to display your <Typography>User Added</Typography>? You must render everything in the component itself depending on the state.
Sorry, but ...
... it looks like a great example of abusing react ... result of skipping basic tutorials, docs and mixing random oudated examples.
async nature of useState (and setState) - you can't expect updated value;
using useEffect param without knowledge how it affects behaviour;
'useEffect' not required at all;
returning components from event handler instead of [data driven] conditional rendering;
if useMutation used you don't need to pass mutation as param to event handler;
you're using Formik (with fields validation) then checking params (fields) in handler is simply unnecessary;
you can simply use variables: { ...state } if variables props names are matching;
Formik manages values, you don't need to duplicate this using local state - any reason?;
Formik has hooks, too ;) use useFormik();
... event handlers, binding...
first of all thank you so much for your time and good willing to help!
It's the first time I am trying to manage the logic of a form using the formik npm library. It's been really easy to setup and nothing is broken.
But I'm having a problem: The form is being submitted anyway and its causing me troubles because it's redirecting to the /profile and it shouldn't.
This is my form:
<Formik
initialValues={{ identifier: "sdf", password: "fgh" }}
validate={values => {
let errors = {};
// REGEX
let regex = !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
if (!values.identifier) {
errors.identifier = "El correo electrónico es requerido";
} else if (regex.test(values.identifier)) {
errors.identifier = "Invalid email address";
}
if (!values.password) {
errors.password = "El email es requerido";
}
return errors;
}}
handleSubmit={(values, { setSubmitting }) => {
// trying to see what im receiving.
// I've seen this code searching around and I wanted to try
console.log(values, setSubmitting);
setTimeout(() => {
// submit them do the server. do whatever you like!
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
}}
onSubmit={values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
}}
render={({
values,
errors,
handleSubmit,
handleChange,
handleBlur
}) => (
<form onSubmit={handleSubmit} name="login">
<Label htmlFor="login__username">
{i18n.FORM.LABEL.USERNAME}
</Label>
{errors.identifier && (
<span color="red">{errors.identifier}</span>
)}
<Input
value={values.identifier}
type="text"
name="login__username"
placeholder={i18n.FORM.LABEL.USERNAME__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="identifier"
/>
<Label htmlFor="login__password">
{i18n.FORM.LABEL.PASSWORD}
</Label>
{errors.password && (
<span color="red">{errors.password}</span>
)}
<Input
value={values.password}
type="password"
name="login__password"
placeholder={i18n.FORM.LABEL.PASSWORD__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="password"
/>
<ActionsWrapper theme={theme}>
<Button type="submit" size="large" fullWidth>
{i18n.PAGE.LOGIN.BUTTON__SUBMIT}
</Button>
</ActionsWrapper>
</form>
)}
/>
And the function that handles the submit of the form is the one below:
handleOnSubmit = values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
};
I've tried to search how people deals with this but, all the examples I've found did not use anything or care about the prevent of the form.
Why is that? What I'm doing wrong? Any advice? Resources?
Thank you so much for everything!
that's not the default behavior of Forms integrated with formik.
Firstly, I think you're missing the "noValidate=true" attribute on your html form element.
Secondly, I don't think you need "handleSubmit" prop on the Formik tag. Just the onSubmit prop works.