i am learning React and working on my pet-project. So, i have a form with custom input components and I want to validate them only when the user clicks on the button, but I don't know how. Full input check is whether the form is not empty and filled out correctly. I tried to create a model for subscribing components to the button click event, but when the form was filled correctly, the states were updated not after 1, but after 2 clicks, and I don't know why.
Code of Form component:
import React from 'react';
import { FormBox } from '../FormBox';
import styles from './checkoutpage.module.scss'
export function CheckoutPage() {
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h2>checkout</h2>
<legend>Contact info</legend>
<FormBox
type='email'
placeholder='Email address'
errorMessage='Enter a valid email address'
/>
<legend>Shipping info</legend>
<FormBox
type='name'
placeholder='Name'
/>
<FormBox
type='text'
placeholder='Streer address'
/>
<FormBox
type='text'
placeholder='Apt / Suite / Other (optional)'
/>
<div className={styles.divideBox}>
<FormBox
type='text'
placeholder='City'
/>
<FormBox
type='text'
placeholder='Province'
/>
</div>
<div className={styles.divideBox}>
<FormBox
type='text'
placeholder='Postal Code'
/>
<FormBox
type='text'
placeholder='Country'
/>
</div>
<FormBox
type='text'
placeholder='Phone Number'
/>
</div>
</div>
);
}
Code of Input component:
import React, { useEffect, useRef, useState } from "react";
import styles from "./formbox.module.scss";
interface IFormBox {
type: string;
placeholder: string;
errorMessage?: string;
}
export function FormBox({ type, placeholder, errorMessage }: IFormBox) {
const [valid, setValid] = useState(true);
const ref = useRef<HTMLInputElement>(null);
const checkValidityChange = () => {
if (ref.current) {
setValid(ref.current.checkValidity());
}
}
useEffect(() => {
if (ref.current) {
if (valid && ref.current.value !== "") {
ref.current.style.backgroundColor = "#e8f0fe";
}
else {
ref.current.style.backgroundColor = "white";
}
}
}, [valid])
return (
<div className={styles.container}>
<input
ref={ref}
onChange={checkValidityChange}
name="formInput" type={type}
placeholder={placeholder}
onBlur={() => {if (ref.current && ref.current.value === "") ref.current.style.backgroundColor = "white";}}
/>
{!valid && errorMessage && (
<span>{'* '.concat(errorMessage)}</span>
)}
</div>
);
}
Method i tried to use:
Form component:
import React, { useEffect, useRef, useState } from 'react';
import { FormBox } from '../FormBox';
import styles from './checkoutpage.module.scss'
export function CheckoutPage() {
const [formValidity, setFormValidity] = useState(false);
const [emailValidity, setEmailValidity] = useState(false);
const [nameValidity, setNameValidity] = useState(false);
const [addressValidity, setAddressValidity] = useState(false);
const [aptValidity, setAptValidity] = useState(false);
const [cityCalidity, setCityValidity] = useState(false);
const [provinceValidity, setProvinceValidity] = useState(false);
const [codeValidity, setCodeValidity] = useState(false);
const [countryValidity, setCountryValidity] = useState(false);
const [phoneValidity, setPhoneValidity] = useState(false);
const refButton = useRef<HTMLButtonElement>(null);
function subscrition (event: MouseEvent, setValidity: (v: boolean) => void, setValidityValue: boolean) {
if (event.target === refButton.current) {
setValidity(setValidityValue);
}
}
const getValidity = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
const result = emailValidity && nameValidity && addressValidity && aptValidity && cityCalidity && provinceValidity && codeValidity && countryValidity && phoneValidity
setFormValidity(result)
console.log(formValidity)
}
return (
<div className={styles.container}>
<form className={styles.formContainer}>
<h2>checkout</h2>
<legend>Contact info</legend>
<FormBox
subscrition={subscrition}
type='email'
placeholder='Email address'
errorMessage='Enter a valid email address'
setFullValidity={setEmailValidity}
/>
<legend>Shipping info</legend>
<FormBox
subscrition={subscrition}
type='name'
placeholder='Name'
setFullValidity={setNameValidity}
/>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Streer address'
setFullValidity={setAddressValidity}
/>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Apt / Suite / Other (optional)'
setFullValidity={setAptValidity}
/>
<div className={styles.divideBox}>
<FormBox
subscrition={subscrition}
type='text'
placeholder='City'
setFullValidity={setCityValidity}
/>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Province'
setFullValidity={setProvinceValidity}
/>
</div>
<div className={styles.divideBox}>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Postal Code'
setFullValidity={setCodeValidity}
/>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Country'
setFullValidity={setCountryValidity}
/>
</div>
<FormBox
subscrition={subscrition}
type='text'
placeholder='Phone Number'
setFullValidity={setPhoneValidity}
/>
<button ref={refButton} onClick={getValidity} type="submit" className={styles.btn}>Place Your Order</button>
{formValidity && (
<span style={{backgroundColor: "green"}}>Form is valid</span>
)}
{!formValidity && (
<span style={{backgroundColor: "red"}}>Form is invalid</span>
)}
</form>
</div>
);
}
Input component:
import React, { useEffect, useRef, useState } from "react";
import styles from "./formbox.module.scss";
interface IFormBox {
type: string;
placeholder: string;
errorMessage?: string;
setFullValidity: (v: boolean) => void;
subscrition: (event: MouseEvent, setValidity: (v: boolean) => void, setValidityValue: boolean) => void;
}
export function FormBox({ type, placeholder, errorMessage, setFullValidity, subscrition }: IFormBox) {
const [valid, setValid] = useState(true);
const ref = useRef<HTMLInputElement>(null);
const checkValidityChange = () => {
if (ref.current) {
setValid(ref.current.checkValidity());
}
}
useEffect(() => {
if (ref.current) {
// @ts-ignore
document.addEventListener('click', (event: MouseEvent) => {subscrition(event, setFullValidity, valid && (ref.current.value !== ""))})
}
})
useEffect(() => {
if (ref.current) {
if (valid && ref.current.value !== "") {
ref.current.style.backgroundColor = "#e8f0fe";
} else {
ref.current.style.backgroundColor = "white";
}
}
}, [valid])
return (
<div className={styles.container}>
<input
ref={ref}
onChange={checkValidityChange}
name="formInput" type={type}
placeholder={placeholder}
onBlur={() => {
if (ref.current && ref.current.value === "") {
ref.current.style.backgroundColor = "white";
}
}}
/>
{!valid && errorMessage && (
<span>{'* '.concat(errorMessage)}</span>
)}
</div>
);
}
Any advice on how to improve the code will be very warmly welcomed. Thanks for reading, and sorry if i waste your time.
You can follow this example to validate input fields. Here input reference is used like ref
to validate data.
WORKING DEMO
import React from "react";
import ReactDOM from "react-dom";
import "./style.css";
function validate(name, email, password) {
// we are going to store errors for all fields
// in a signle array
const errors = [];
if (name.length === 0) {
errors.push("Name can't be empty");
}
if (email.length < 5) {
errors.push("Email should be at least 5 charcters long");
}
if (email.split("").filter(x => x === "@").length !== 1) {
errors.push("Email should contain a @");
}
if (email.indexOf(".") === -1) {
errors.push("Email should contain at least one dot");
}
if (password.length < 6) {
errors.push("Password should be at least 6 characters long");
}
return errors;
}
class SignUpForm extends React.Component {
constructor() {
super();
this.state = {
errors: []
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
const name = ReactDOM.findDOMNode(this._nameInput).value;
const email = ReactDOM.findDOMNode(this._emailInput).value;
const password = ReactDOM.findDOMNode(this._passwordInput).value;
const errors = validate(name, email, password);
if (errors.length > 0) {
this.setState({ errors });
return;
}
// submit the data...
}
render() {
const { errors } = this.state;
return (
<form onSubmit={this.handleSubmit}>
{errors.map(error => (
<p key={error}>Error: {error}</p>
))}
<input
ref={nameInput => (this._nameInput = nameInput)}
type="text"
placeholder="Name"
/>
<input
ref={emailInput => (this._emailInput = emailInput)}
type="text"
placeholder="Email"
/>
<input
ref={passwordInput => (this._passwordInput = passwordInput)}
type="password"
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<SignUpForm />, rootElement);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.