I have a formik form with a select field; two options. When i use onClick I always get "yes" submitted and if i use onChange it does not work in that it does not allow me to choose anything, just always leaves the field the same as before.
I have read a ton of different things. I have tried the setFieldValue, and I have tried onBlur, I have tried numerous different ways in writing the onChange handler without any success. Maybe i am just not changing and writing it properly.
I have looked over the suggested questions already on here and for whatever reason i can not get them to work in my code.
import React, { useState, useRef } from 'react';
import { Form, Field, } from 'formik';
import emailjs from '#emailjs/browser';
const RsvpForm = ({ errors, touched, isValid, dirty }) => {
const form = useRef();
const sendEmail = (e) => {
e.preventDefault();
const userName = e.target[0].value;
const email = e.target[1].value;
const attending = state;
const plusOne = plusone;
const guests = e.target[4].value;
const guestNumber = e.target[5].value;
const guest_name = e.target[6].value;
const song = e.target[7].value;
const message = e.target[8].value;
let templateParams = {
userName: userName,
email: email,
attending: attending,
plusOne: plusOne,
guests: guests,
guestNumber: guestNumber,
guest_name: guest_name,
song: song,
message: message,
};
emailjs.send(process.env.REACT_APP_SERVICE_ID, process.env.REACT_APP_TEMPLATE_ID, templateParams, process.env.REACT_APP_PUBLIC_KEY)
.then((result) => {
console.log(result.text);
e.target.reset();
}, (error) => {
console.log(error.text);
});
};
const[state, attendingState] = useState("");
const onClick = (e) => {
let{value} = e.target;
if(value=== 'yes') {
attendingState('yes')
}else {
attendingState('no')
}
}
const[plusone, plusOnestate] = useState("");
const onPick = (e) => {
let{value} = e.target;
if(value=== 'no') {
plusOnestate('no')
}else {
plusOnestate('yes')
}
}
return (
<div className='form-group'>
<label className='col-form-label'>Plus One:</label>
<Field
component='select'
className={
touched.plusOne
? `form-control ${errors.plusOne ? 'invalid' : 'valid'}`
: `form-control`
}
name='plusOne'
type='select'
// onChange={(e) => setFieldValue('plusOne', e.target.value)}
onClick={onPick}
>
<option value="">Please select an answer</option>
<option value="yes">Yes, please add a plus one or few</option>
<option value="no">Just me!</option>
</Field>
{touched.plusOne && errors.plusOne && (
<small className='text-warning'><strong>{errors.plusOne}</strong></small>
)}
</div>
I'm creating a contact form, and attempting to setState for each field, then pass that value to an email form.
When I console.log, I'm not getting any output. What am I doing wrong here?
Should I be using useEffect? From what I understand, useEffect is called whenever I set state, so I shouldn't need to. Is there something else I'm missing or doing wrong?
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
setName(document.getElementById('name').value)
setEmail(document.getElementById('email').value)
setSubject(document.getElementById('subject').value)
setMessage(document.getElementById('message').value)
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
Set state is an asynchronous operation, so log the state after setting it, will log the old value.
If you want to sure that the value has been setten correctly, you need to use setState.
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
setShowSuccessMessage(prev => {
console.log(prev)
return prev
})// log the 'Contact form submitted'
setShowFailureMessage(prev => {
console.log(prev)
return prev
})// log false
And setting state, in this case, will not make the component re-render as you return the same value.
The reason is that setXxx methods are async. If you click submit 10 times fast enough, you will see different results.
You have two options:
not preferred: If you don't use 'controlled' input (you are not using it now - in input elements, you are not using things like 'value={name}'). then you don't need the state at all.
Use controlled input. You can check document on reactjs org.
Simply put,
useState
on the input tag, add value={name} onChange={changeHandler},
in changHandler, setState
in formSubmit, use the value in state
There is a section on forms on this excellent tutorial:
Bob Ziroll free React Course
You are using react so you need to set data and use those data after that i have updated your code it should work for you. i.e. you need to set value in onChange function as below:-
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
const style = {};
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' onChange={(e) => setName(e.target.value)} />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' onChange={(e) => setEmail(e.target.value)} />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' onChange={(e) => setSubject(e.target.value)} />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
onChange={(e) => setMessage(e.target.value)}
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
And if you want same for phone you can also set data for the same and use after that.
I am getting this error when trying to type a password longer than 6 characters in the following react form
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
for each character the user inputs in password/confirm password, I need to validate them.
How can I do this avoiding this infinite loop?
export const Signup: FC<InjectedFormProps<string>> = (props) => {
const {handleSubmit} = props
const {t} = useTranslation()
const [password, setPassword] = useState();
const [confirmPassword, setConfirmPassword] = useState();
const [isValid, setValidity] = useState(false);
const errorMessage = useSelector(getErrorMessage)
const onChangePassword = (event: any) => {
setPassword(event.target.value)
}
const onChangeConfirmPassword = (event: any) => {
setConfirmPassword(event.target.value)
}
const validatePasswords = () => {
if (password && confirmPassword) {
setValidity(password === confirmPassword)
}
}
useEffect(() => {
validatePasswords();
}, [password, confirmPassword, isValid])
const minLength = minLengthPassword(6)
return (
<form onSubmit={handleSubmit}>
<FRow className="full-width">
<FGroup>
<Field validate={minLength} onChange={onChangePassword} name="password" component="input" type="password" placeholder="Password"/>
</FGroup>
</FRow>
<FRow className="full-width">
<FGroup>
<input onChange={onChangeConfirmPassword} name="confirmPassword" type="password" placeholder="Confirm Password"/>
</FGroup>
</FRow>
<FRow>
{isValid ? '' : <Error>{t("auth.lbl_passwords_must_match")}</Error>}
</FRow>
<FullWidthSvgButton disabled={!isValid}>
<span>{t("buttons.btn_sign_up")}</span>
</FullWidthSvgButton>
{errorMessage && (
<Error>{errorMessage}</Error>
)}
</form>
)
}
export const SignupForm = reduxForm<any>({
form: "Signup",
})(Signup)
export default SignupForm
Try to remove isValid from the dependencies.
useEffect(() => {
validatePasswords();
}, [password, confirmPassword])
You should run your setValidity only when password or confirmPassword changed. If you call it when your isValid changed - you are getting the infinite loop (because setValidity changes isValid)
Please update your hooks like the following.
Wrap validatePasswords by useCallback with dependencies of 2 states.
And add this function in dependency of useEffect.
const validatePasswords = useCallback( () => {
if (password && confirmPassword) {
setValidity(password === confirmPassword)
}
}, [password, confirmPassword])
useEffect(() => {
validatePasswords();
}, [validatePasswords])
I wanna activate the button after I check all the inputs from the user by the function ValidateForm(). If all of fields are correct due to validateForm(), then I can see the green button activate. The problem: how can I check ALL of the fields after finishing filling out, and not only when I fill out the first field.
const validateForm = (values) => {
const errors = {};
errors.firstname = values.firstname.length >= 3;
errors.lastname = values.lastname.length >= 3;
errors.email = values.email.length >= 3;
return errors;
}
const SignUp = () => {
const eintraege = { firstname: '', lastname: '', email: '' };
const [formValues , setFormValues] = useState(eintraege);
const [errors, setErrors] = useState({});
const [buttonDisabled, setButtonDisabled] = useState(true);
const formChangeHandler = (event) => {
const { name, value } = event.target;
const nextValues = {...formValues, [name]: value };
setErrors(validateForm(nextValues));
};
const handleSubmit = (event) => {
event.preventDefault();
setButtonDisabled(true);
};
const btnChanged = () => {
return (Object.keys(errors).length === 0) ?
setButtonDisabled(true) :
setButtonDisabled(false) ;
};
return (
<FormBox onSubmit={handleSubmit}>
<ProfileImage />
<InputWrapper gridPosition="firstname-input">
<Input
type="text"
name='firstname'
value={formValues.firstname}
placeholder='Vorname'
hasError={errors.firstname === false}
onChangeHandler={formChangeHandler}
/>
<Error errors={errors} name="firstname" />
</InputWrapper>
<Button className="btn" disabled={buttonDisabled}/>
</FormBox>
);
}
I am new to React 16, Please find the below code which I am using for Validation using hooks. I have used a reusable Select box and on the value of the option, I am showing an input box.
Validation rules: if the select box is empty on click of submit i want to show error and hide it when I select a value then if the input box is empty want to show error and hide when we give valid input.
Problem : When error message appears for select box and after selecting the value when input box appears it shows the error for that too so it's like the first look of input box is with the validation error. I want to show it when user clicks submit button and onchange after valid entry it should go away.
Really appreciate the help!
const Step3 = (props) => {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSent, setIsSent] = useState(false);
const { handleChange, handleSubmit } = useForm(validate);
useEffect(() => { if (Object.keys(errors).length === 0 && isSubmitting) { } }, [errors]);
const onChange = (event) => {
event.persist && event.persist();
setIsSubmitting(false);
const newValues = {...values,[event.target.name]:event.target.value};
isSent && setErrors(validate(newValues));
setValues(values => newValues);
};
const handleSubmit = (event) => {
if (event) event.preventDefault();
setErrors(validate(values));
setIsSubmitting(true);
setIsSent(true);
};
return (
<div>
<form onSubmit={handleSubmit} noValidate>
<Select
name="abc"
label="ABC"
options={options}
onChange={onChange}
/>
{errors.abc && (<p className="help is-danger">{errors.abc}</p>)}
{values.abc=='Apple' && <input name="xyz" value={values.xyz||'' onChange={onChange}}/>
{errors.xyz && (<p className="help is-danger">{errors.xyz}</p> )}
}
<Button type={submit}>
Submit
</Button>
</form>
</div>
);
};
function validate(values) {
let errors = {}
if (!values.abc)
{ errors.abc= ' required'; }
if (!values.xyz) {
errors.xyz= 'required';
}
return errors;
};
useForm({ mode: 'onChange' })
Doc:
https://react-hook-form.com/api#useForm
Example:
https://codesandbox.io/s/react-hook-form-defaultvalues-v6-forked-s9buh?file=/src/index.js