This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 10 months ago.
My form Submission would skip showing validation error message when submitting wrong input. How can I prevent submission when error message is present?
my code:
import React, { useState } from "react";
const App = () => {
const [formData, setFormData] = useState({ name: "" });
const [formError, setFormError] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);
const validate = () => {
const errors = {};
console.log("2. errors: ", errors); //<=== point 2.
if (formData.name.length === 0) {
errors.name = "name required";
}
console.log("3. errors: ", errors); //<=== point 3.
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmit(true);
console.log("1. formData: ", formData); //<=== point 1.
setFormError(validate(formData));
console.log("4. formError: ", formError); //<=== point 4.
// This is true only if formError is empty object
if (
Object.keys(formError).length === 0 &&
formError.constructor === Object
) {
console.log("5. sending data: ", formData.name); //<=== point 5.
// Sending data to API here
setIsSubmit(false);
setFormSubmitted(true);
return;
}
setIsSubmit(false);
return;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<>
{formSubmitted ? (
<p>Thank you</p> //<=== Thank you prhase
) : (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
{formError.name && <p>{formError.name}</p>}
{isSubmit ? (
<p>Sending...</p>
) : (
<input type="submit" value="Submit" />
)}
</form>
)}
</>
);
};
export default App;
console when submitting empty input (wrong input):
1. formData: {name: ''}
2. errors: {}
3. errors: {name: 'name required'}
4. formError: {}
5. sending data:
console when submitting with input "test" (right input):
1. formData: {name: 'test'}
2. errors: {}
3. errors: {}
4. formError: {}
5. sending data: test
When I submit a right input, the form would act as I wanted.
submit right input -> input validation -> no error found -> send data -> show "Thank you" phrase
But if I submit wrong input.
submit wrong input -> input validation -> error found -> append error message -> but still return empty object (to formError) -> send empty input -> show "Thank you" phrase
The problem is at point 4, the validate() function wont return the error message. On my actual file with API, while submitting wrong input it will show the error message (e.g. formError.name) (takes 1 to 2 seconds to send data to CMS) but the submission will still goes thru and show "thank you" phrase. How can I fix this? Thank you.
State update doesn't happen synchronously.
You can extract the error in a variable (named errors in the example below) and do the conditional logic based on that instead of the state.
import React, { useState } from "react";
const App = () => {
const [formData, setFormData] = useState({ name: "" });
const [formError, setFormError] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);
const validate = () => {
const errors = {};
if (formData.name.length === 0) {
errors.name = "name required";
}
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmit(true);
const errors = validate(formData); // extracting here
setFormError(errors); // setting state using the extracted value
// validating using the extracted value
if (Object.keys(errors).length === 0 && errors.constructor === Object) {
// Sending data to API here
setIsSubmit(false);
setFormSubmitted(true);
return;
}
setIsSubmit(false);
return;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<>
{formSubmitted ? (
<p>Thank you</p> //<=== Thank you prhase
) : (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
{formError.name && <p>{formError.name}</p>}
{isSubmit ? (
<p>Sending...</p>
) : (
<input type="submit" value="Submit" />
)}
</form>
)}
</>
);
};
export default App;
Related
Edited
I am trying to make a form that will enable the user to edit an existing citation. The citation is pulled from the backend with an api call. I then assign the values grabbed from the api to the state variables. These state variables is passed as props to the form jsx where they are used in the value prop of the input field. But for some reason the values assigned to state is not retained by the state and it goes back to its initial state values. I am adding the code as to how the component looks like
const EditEnterCitation = () => {
const [searchParams, setSearchParams] = useSearchParams();
// getting params from the url to pass to the functions
const cat = searchParams.get("cat");
const { id } = useParams();
// state to pass to the value prop in the form input
const [formData, setFormData] = useState({
institution_name: "0",
judge_name: "",
case_no: "",
apelLate_type: "0",
law: "",
point_of_law: "0",
party_name_appelant: "",
party_name_respondent: "",
advocate_petitioner: "",
advocate_respondent: "",
judgements: "",
date_of_order: "",
headnote: "",
references: "",
equivalent_citations: "",
title: "",
});
// state to for select buttonn in the form
const [instName, setInstName] = useState([]);
const [lawChoiceOptions, setlawChoiceOptions] = useState([]);
const [pointOfLawOptions, setPointOfLawOptions] = useState([]);
const [appealateType, setAppealateType] = useState([]);
// getting the choices for select from backend
const allChoices = useQuery(
["allChoices", cat],
() => getAllChoices(cat),
{
enabled: cat !== null,
refetchOnWindowFocus: false,
onSuccess: (response) => {
// setting law choices
if (response.data.law) {
let arr = response.data.law.map((element, index) => {
return { value: element.law_name, label: element.law_name };
});
setlawChoiceOptions(arr);
}
// setting point of law options
setPointOfLawOptions(response.data.pol);
// setting appealate type
setAppealateType(response.data.appeal);
// setting ins choices
if (response.data.ins.length) {
setInstName(response.data.ins);
} else {
let court_names = [];
let tribunal_name = [];
if (response.data.ins.ins_court.length > 0) {
court_names = response.data.ins.ins_court;
}
if (response.data.ins.ins_tribunal.length > 0) {
tribunal_name = response.data.ins.ins_tribunal;
}
let ins_names = court_names.concat(tribunal_name);
setInstName(ins_names);
}
},
}
);
// function to get default values of the citation
const getDefaultValues = useQuery(
["detailCitation", id],
() => detailCitation(cat, id),
{
enabled: allChoices.isFetched === true,
refetchOnWindowFocus: false,
onSuccess: (response) => {
setFormData({
institution_name: response.data.institution_name,
judge_name: response.data.judge_name,
case_no: response.data.case_no,
apelLate_type: response.data.apelLate_type,
law: response.data.law,
point_of_law: response.data.point_of_law,
party_name_appelant: response.data.party_name_appelant,
party_name_respondent: response.data.party_name_respondent,
advocate_petitioner: response.data.advocate_petitioner,
advocate_respondent: response.data.advocate_respondent,
judgements: response.data.judgements,
date_of_order: response.data.date_of_order,
headnote: response.data.headnote,
references: response.data.references,
equivalent_citations: response.data.equivalent_citations,
title: response.data.title,
});
},
}
);
// handling form data change for some fields
const handleFormDataChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
// handling form data change for the judgement
const handleJudgementData = (data) => {
setFormData({ ...formData, judgements: data });
};
const handlePartyAppealData = (data) => {
setFormData({ ...formData, party_name_appelant: data });
};
const handlePartyRespondData = (data) => {
setFormData({ ...formData, party_name_respondent: data });
};
// handling data change of the law choices
const handleOnChangeLaw = (selectedOption) => {
let newArray = [];
selectedOption.map((element) => {
newArray.push(element.value);
});
setFormData({ ...formData, law: newArray.toString() });
};
// hadling sumbimission of citation
const handleFormDataSubmit = async (e) => {
e.preventDefault();
try {
setProgress(80);
let response = await addCitation(cat, formData);
if (response.status === 201) {
toastNotification(
`Citation Uploaded to ${cat.toUpperCase()}`,
`success`
);
setProgress(100);
goToTop();
} else {
toastNotification(`Server Error. Could not upload citation`, `error`);
setProgress(100);
}
} catch (error) {}
};
useEffect(() => {
console.count("formData Appearing");
console.log(formData);
});
return (
<>
<LoadingBar
color="red"
progress={progress}
onLoaderFinished={() => setProgress(0)}
height={4}
/>
<Helmet>
<title>Enter Citation</title>
</Helmet>
<Wrapper>
<FormContainer onSubmit={handleFormDataSubmit}>
<EditInsFormElements
cat={cat}
handleFormDataChange={handleFormDataChange}
formData={formData}
handleJudgementData={handleJudgementData}
handleOnChangeLaw={handleOnChangeLaw}
instName={instName}
/>
<EditOtherFormElements
cat={cat}
handleFormDataChange={handleFormDataChange}
formData={formData}
handleJudgementData={handleJudgementData}
handleOnChangeLaw={handleOnChangeLaw}
lawChoiceOptions={lawChoiceOptions}
pointOfLawOptions={pointOfLawOptions}
appealateType={appealateType}
handlePartyAppealData={handlePartyAppealData}
handlePartyRespondData={handlePartyRespondData}
/>
<FormFooter>
<UploadBtn disabled={disableSubmit()} type="submit">
Upload Citation
</UploadBtn>
</FormFooter>
</FormContainer>
</Wrapper>
</>
);
};
the jsx inside the EditInsFormElements and EditOtherFormElements looks like this
<label className="required-field" htmlFor="apelLate_type">
Apellate Type*
</label>
<select
name="apelLate_type"
id="apelLate_type"
value={formData.apelLate_type}
onChange={handleFormDataChange}
required
>
<option value="0" disabled hidden>
Select
</option>
{appealateType &&
appealateType.length &&
appealateType.map((element, index) => {
return (
<option key={index} value={element.appealate_type}>
{element.appealate_type}
</option>
);
})}
</select>
<label className="required-field" htmlFor="case_no">
Case No*
</label>
<TextArea
name="case_no"
id="case_no"
value={formData.case_no}
onChange={handleFormDataChange}
required
/>
<label className="required-field" htmlFor="title">
Title*
</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleFormDataChange}
required
/> ....
And the state behaviour in the console that I have produced using useEffect is attached below
Question
Why is this alternating behaviour seen in the console is caused and how to rectify it so that the state retains the values grabbed and assigned from the backend.
So after debugging I founf out that the problem was with the CkEditor component I was using, whose onChange function was creating this behaviour.
the way I was using the onChange function on one of the ckeditor component was like below
<CKEditor
editor={Editor}
data={formData.judgements}
name="judgements"
id="judgements"
onChange={(event, editor, e) => {
const data = editor.getData()
handleJudgementsData(data)
} }
/>
and the onChange function looked like this
const handleJudgementData = (data) => {
setFormData({ ...formData, judgements: data });
};
This was causing the problem as the state was not updating synchronously what I could understand from Phil's answer in the question he linked
so I changed the code in the below way and now it is working
jsx
<CKEditor
editor={Editor}
data={formData.judgements}
onChange={(event, editor, e) => handleJudgementData(event, editor, e)}
/>
onChnage function
const handleJudgementData = (event, editor) => {
let data = editor.getData();
setFormData((prev) => ({ ...prev, judgements: data }));
};
After doing it the above way now the state retains the data fetched from api
I have some front-end validation for formatting some inputs on a form. Currently my errors message print to the console. I would like it so that these error message became the innerHTML of a heading in the component.
I have already tried assigning the innerHTML with document.getElementByID but it hasnt worked. Ive additionally tried defining the error variable outside of scope or adding it between the h2 tags as {error}
I would like any suggestions to make these error messages display as text on the front-end rather than being printed to console as they are now.
The blank h2 element is the element im trying to target the error text towards.
import React, {useState} from 'react';
import {useNavigate} from 'react-router-dom';
import axios from 'axios';
import Helmet from 'react-helmet';
import Button from '#mui/material/Button';
export default function Register() {
const navigate = useNavigate();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const inputs = [
{
placeholder: 'First name',
setState: setFirstName
},
{
placeholder: 'Last name',
setState: setLastName
},
{
placeholder: 'Email',
setState: setEmail
},
{
placeholder: 'Enter a password',
setState: setPassword,
},
{
placeholder: 'Confirm password',
setState: setConfirmPassword,
},
]
//Insert into database api request
const insertRow = () => {
axios.post('/users/insert', {
firstName: firstName,
lastName: lastName,
email: email,
password: password,
})
};
//#region Validation
const atSymbol = '#';
//Checks for numbers in string
function containsNumber(str) {
return /[0-9]/.test(str);
}
//Checks for capital in string
function containsCapital(str) {
return /[A-Z]/.test(str);
}
const submitHandler = (e) => {
e.preventDefault(); //Prevents page refresh
let error = ''
//If no # symobol in email
if (!email.includes(atSymbol)) {
error = 'Please enter a valid email';
console.log(error);
return;
}
//If password doesn't contain a capital
if (!containsCapital(password)) {
error = 'Password must contain at least one uppercase letter';
console.log(error);
return;
}
//If password doesn't contain a number
if (!containsNumber(password)) {
error = 'Password must contain at least one number';
console.log(error);
return;
}
//If password is less than 8 characters
if (password.length < 8) {
error = 'Password must be at least 8 characters long';
console.log(error);
return;
}
//If passwords don't match
if (confirmPassword !== password) {
error = 'Passwords do not match';
console.log(error);
return;
}
//If all validation passes
insertRow();
navigate('/login');
}
//#endregion
return (
<>
<Helmet>
<title>Title | Register</title>
</Helmet>
<div className="pt-36 sm:pt-44 pb-20 md:pb-48 max-w-[1200px] mx-5 lg:mx-auto space-y-5">
<div className="bg-red-300 max-w-[500px] p-1 mx-auto">
<h2 className="text-lg text-red-900 font-semibold"></h2>
</div>
<h1 className="text-2xl font-semibold text-center">Register</h1>
<form onSubmit={submitHandler} className="flex flex-col space-y-5 max-w-[500px] mx-auto">
{inputs.map((items, index) => (
<input
key={index}
type="text"
className="border-2 border-black p-1"
placeholder={`${items.placeholder} *`}
required
onChange={(e) => {
items.setState(e.target.value);
}}
/>
))}
<Button
type="submit"
sx={{
border: '2px solid #000000',
color: '#000000',
marginLeft: 'auto',
marginRight: 'auto',
':hover': {
bgcolor: '#ffffff',
color: '#000000',
},
}}
>
Submit
</Button>
</form>
</div>
</>
)
}
You cannot do directly {error} as error is not defined as a state property in the component.
You have to declare it
const [error, setError] = useState('');
and whenever you set error you have to use setError
if (!email.includes(atSymbol)) {
setError('Please enter a valid email');
console.log(error);
return;
}
then in JSX you can use
{error.length>0 && <p>{error}</p>}
although it will be a common error for all the inputs
To keep track of the errors for every field you could take this approach.
An object for all the errors with the field name as object key. Update the state with the new error. Now this will not keep track of multiple errors for every field but will eventually pass all of them. Before insertRow check if there are any errors, if any return.
const [errors, setErrors] = useState({ email: "", password: "" });
const inputs = [
{
email: 'email', // key to get value from errors
placeholder: 'Email',
setState: setEmail
},
...
]
const submitHandler = (e) => {
e.preventDefault(); //Prevents page refresh
setErrors({ email: "", password: "" }); // reset the errors on every submit
//If no # symobol in email
if (!email.includes(atSymbol)) {
setErrors((prevError) => ({
...prevError,
email: "Please enter a valid email",
}));
// no return needed
}
// ... at the end before navigating check if there are errors
if (errors.email || errors.password) return;
//If all validation passes
insertRow();
navigate('/login');
};
The extra name prop comes into play when we want to display the errors. After every input we check if there is an error for that field and display it if any.
return (
...
{inputs.map((items, index) => (
<>
<input
key={index}
type="text"
className="border-2 border-black p-1"
placeholder={`${items.placeholder} *`}
required
onChange={(e) => {
items.setState(e.target.value);
}}
/>
{ errors[items.name] && <div>{errors[items.name]}</div>}
</>
))}
...
);
You cannot do directly {error} as error is not defined as a state property in the component.
You have to declare it
const [error, setError] = useState('');
and whenever you set error you have to use setError
if (!email.includes(atSymbol)) {
setError('Please enter a valid email');
console.log(error);
return;
}
then in JSX you can use
{error.length>0 && <p>{error}</p>}
although it will be a common error for all the inputs
for the solution of #kaneki21 i prefer this solution for JSX
{error?.length>0? <p>{error}</p>:null}
in case if error is "undefined" or "null"
So basically I've a Login form with two input fields (password, email) and a react-google-recaptcha. My use case is simple. Test if the submit button is disabled if the input fields are empty and recaptcha is not verified. Enable it only when input fields contain data and recaptcha is verified.
Below is the code that I wrote and I know I did something wrong with recaptcha verification in test file.
I've gone through existing answers in Stack Overflow for example this but facing problem implementing the same.
Login.tsx
import React, { useState } from "react";
import ReCAPTCHA from "react-google-recaptcha";
const Login = () => {
const [creds, setCreds] = useState({
email: "",
pw: "",
});
const [isCaptchaVerified, setIsCaptchaVerified] = useState(false);
const handleCaptchaChange = (value): void => {
if (value !== null) setIsCaptchaVerified(true);
else setIsCaptchaVerified(false);
};
return (
<div>
<input
data-testid="email-testid"
type="email"
name="email"
value={creds.email}
onChange={(e) => {
setCreds({
email: e.target.value,
pw: creds.pw,
});
}}
/>
<input
data-testid="pw-testid"
type="password"
name="password"
value={creds.pw}
onChange={(e) => {
setCreds({
pw: e.target.value,
email: creds.email,
});
}}
/>
<ReCAPTCHA
data-testid="login-recaptcha"
sitekey={siteKey}
onChange={handleCaptchaChange}
/>
<button data-testid="submit-testid" disabled={!isCaptchaVerified || !creds.pw ||
!creds.email}>
Submit
</button>
</div>
);
};
export default Login;
Login.test.tsx
test("test if button is disabled untill captcha is verified",()=> {
const loginRecaptcha = screen.findByTestId('login-recaptcha');
const emailField = screen.findByTestId('email-testid');
const pwField = screen.findByTestId('pw-testid');
const submitButton = screen.findByTestId('submit-testid');
expect(submitButton).toBeDisabled();
fireEvent.change(emailField, { target: { value: "user#test.com" } });
fireEvent.change(pwField, { target: { value: "user#1234" } });
fireEvent.click(loginRecaptcha);
expect(submitButton).not.toBeDisabled();
})
I have a basic form with two inputs: email and confirmEmail, which updates the email address and also confirms if the new email address was typed correctly.
So far validation works also fine. Whenever email doesn't match with the confirmEmail or one of the inputs is empty, it will throw an error.
However, I want to put all this validation to the submit button, so that validation worked and errors are highlighted only once button is clicked, and update registeredEmail state if input value was valid.
Here is the code snippet and sandbox link.
import React, { useState } from "react";
function Form() {
const [registeredEmail, setRegisteredEmail] = useState("JohnDoe#gmail.com");
const [input, setInput] = useState({
email: "",
confirmEmail: ""
});
const [error, setError] = useState({
email: "",
confirmEmail: ""
});
const onInputChange = (e) => {
const { name, value } = e.target;
setInput((prev) => ({
...prev,
[name]: value
}));
validateInput(e);
};
const validateInput = (e) => {
let { name, value } = e.target;
setError((prev) => {
const stateObj = { ...prev, [name]: "" };
switch (name) {
case "email":
if (!value) {
stateObj[name] = "Please enter Email";
} else if (input.confirmEmail && value !== input.confirmEmail) {
stateObj["confirmEmail"] =
"Email and Confirm Email does not match.";
} else {
stateObj["confirmEmail"] = input.confirmEmail
? ""
: error.confirmEmail;
}
break;
case "confirmEmail":
if (!value) {
stateObj[name] = "Please enter Confirm Email.";
} else if (input.email && value !== input.email) {
stateObj[name] = "Email and Confirm Email does not match.";
}
break;
default:
break;
}
return stateObj;
});
};
const handleSubmit = (e) => {
e.preventDefault();
validateInput(e);
setRegisteredEmail(input.email);
};
return (
<>
<header>{registeredEmail}</header>
<form
style={{
display: "flex",
flexDirection: "column"
}}
>
<input
type="email"
name="email"
placeholder="address"
onChange={onInputChange}
value={input.email}
/>
{error.email && <span style={{ color: "red" }}>{error.email}</span>}
<input
onChange={onInputChange}
value={input.confirmEmail}
type="email"
name="confirmEmail"
placeholder="repeat address"
/>
{error.confirmEmail && (
<span style={{ color: "red" }}>{error.confirmEmail}</span>
)}
</form>
<button onClick={handleSubmit}>speichern</button>
</>
);
}
export default Form;
Any help will be appreciated
name is an attribute and needs function getAttribute(...) to be fetched.
Try this:
var name = e.target.getAttribute('name');
UPDATE
This won't work because the real problem is that you are checking inside the event of the button that submitted. So you don't have the inputs info and values. You should check the input state and validate those (Here you can set the errors). Then you can return a boolean to decide if the user can submit or not.
Try this:
const validateInput = () => {
if (input.email === "") {
setError({ ...error, email: "Please enter Email" });
return false;
}
if (input.email !== input.confirmEMail) {
setError({
...error,
confirmEmail: "Email and Confirm Email does not match."
});
return false;
}
// HERE YOU CAN ADD MORE VALIDATIONS LIKE ABOVE
return true;
};
const handleSubmit = (e) => {
e.preventDefault();
const isValid = validateInput();
if (isValid) {
//SubmitFunc()
}
};
You currently have your onInputChange handler run validateInput, which then sets the error. You may want to have it run validateInput only in your handleSubmit handler and only use onInputChange to handle state changes on keystrokes as you currently do.
I am setting a state variable with useState whether or not a recaptcha has been clicked, but when I use the setState function my captcha variable doesn't pass ot my on submit, don't understand why, If I remove the setState the captcha variable passes just fine to my onSubmit.
If I remove setSubmitButton(false) everything works fine, not quite sure why.
When I run the setSubmittButton(false) captcha is endefined in my submit function when I dont have it there I get the correct captcha value in my submit function.
import React, { useState } from "react"
import { useForm } from "react-hook-form"
import ReCAPTCHA from "react-google-recaptcha"
const ContactForm = () => {
const { register, handleSubmit, watch, errors } = useForm()
const isBrowser = typeof window !== `undefined`
let location
if (isBrowser) {
location = window.location.hostname
}
let fetchUrl
if (location === "localhost") {
fetchUrl = `http://localhost:8888/.netlify/functions/contact`
} else if (location === "fsdf.gtsb.io") {
fetchUrl = `https://fdsfd/.netlify/functions/contact`
} else {
fetchUrl = "/.netlify/functions/contact"
}
console.log(fetchUrl)
const onSubmit = async data => {
setLoading(true)
console.log(captcha, "captcha value final")
const response = await fetch(fetchUrl, {
method: "POST",
body: JSON.stringify({ data, captcha: captcha }),
})
.then(response => response.json())
.then(data => {
console.log(data)
if (data.captcha === false) {
setCaptchaFailed(true)
}
})
.catch(error => {
console.error("Error:", error)
})
}
const [submitButton, setSubmitButton] = useState(true)
const [loading, setLoading] = useState(false)
const [captchaFailed, setCaptchaFailed] = useState(false)
let captcha
function onChange(value) {
setSubmitButton(false) // IF I REMOVE THIS LINE EVERYTHING WORKS FINE ******
console.log("Captcha value:", value)
captcha = value
}
function error(value) {
alert(value)
}
return (
<>
{/* "handleSubmit" will validate your inputs before invoking "onSubmit" */}
{!loading ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label for="name">Name</label>
<input name="name" ref={register({ required: true })} />
{errors.name && <span>This field is required</span>}
</div>
<div>
<label for="email">Email</label>
<input
type="email"
name="email"
ref={register({ required: true })}
/>
{errors.email && <span>This field is required</span>}
</div>
<div>
<label for="message">Message</label>
<textarea name="message" ref={register({ required: true })} />
{errors.message && <span>This field is required</span>}
</div>
<ReCAPTCHA
sitekey="fdsfsa"
onChange={onChange}
onErrored={error}
/>
<input
type="submit"
className={submitButton ? "disabled" : ""}
disabled={submitButton ? "disabled" : ""}
/>
</form>
) : (
<>
{captchaFailed ? (
<>
<p>Captcha Verification Failed</p>
</>
) : (
<h1>Loading</h1>
)}
</>
)}
</>
)
}
export default ContactForm
You should store the captcha value in a state variable (or ref) instead of a plain JS variable. The plain JS variable will reset to undefined when the component re-renders (after the setSubmitButton changes the state).
const [ captchaValue, setCaptchaValue ] = useState(null);
function onChange(value) {
setSubmitButton(false);
console.log("Captcha value:", value);
setCaptchaValue(value);
}
const onSubmit = async data => {
setLoading(true)
console.log(captchaValue, "captcha value final");
...
}
Tip: Also, make sure to replace all the occurrences of the for html attributes to htmlFor. You can use an HTML-to-JSX transformer for automating such tasks.