如何向 React 组件中的表单添加验证?

我的联系人页面表单如下,

<form name="contactform" onSubmit={this.contactSubmit.bind(this)}>
<div className="col-md-6">
<fieldset>
<input ref="name" type="text" size="30" placeholder="Name"/>
<br/>
<input refs="email" type="text" size="30" placeholder="Email"/>
<br/>
<input refs="phone" type="text" size="30" placeholder="Phone"/>
<br/>
<input refs="address" type="text" size="30" placeholder="Address"/>
<br/>
</fieldset>
</div>
<div className="col-md-6">
<fieldset>
<textarea refs="message" cols="40" rows="20"
className="comments" placeholder="Message"/>
</fieldset>
</div>
<div className="col-md-12">
<fieldset>
<button className="btn btn-lg pro" id="submit"
value="Submit">Send Message</button>
</fieldset>
</div>
</form>

需要为所有字段添加验证。有人能帮我在这个反应表单中添加验证吗?

553180 次浏览

You should avoid using refs, you can do it with onChange function.

On every change, update the state for the changed field.

Then you can easily check if that field is empty or whatever else you want.

You could do something as follows :

class Test extends React.Component {
constructor(props) {
super(props);


this.state = {
fields: {},
errors: {},
};
}


handleValidation() {
let fields = this.state.fields;
let errors = {};
let formIsValid = true;


//Name
if (!fields["name"]) {
formIsValid = false;
errors["name"] = "Cannot be empty";
}


if (typeof fields["name"] !== "undefined") {
if (!fields["name"].match(/^[a-zA-Z]+$/)) {
formIsValid = false;
errors["name"] = "Only letters";
}
}


//Email
if (!fields["email"]) {
formIsValid = false;
errors["email"] = "Cannot be empty";
}


if (typeof fields["email"] !== "undefined") {
let lastAtPos = fields["email"].lastIndexOf("@");
let lastDotPos = fields["email"].lastIndexOf(".");


if (
!(
lastAtPos < lastDotPos &&
lastAtPos > 0 &&
fields["email"].indexOf("@@") == -1 &&
lastDotPos > 2 &&
fields["email"].length - lastDotPos > 2
)
) {
formIsValid = false;
errors["email"] = "Email is not valid";
}
}


this.setState({ errors: errors });
return formIsValid;
}


contactSubmit(e) {
e.preventDefault();


if (this.handleValidation()) {
alert("Form submitted");
} else {
alert("Form has errors.");
}
}


handleChange(field, e) {
let fields = this.state.fields;
fields[field] = e.target.value;
this.setState({ fields });
}


render() {
return (
<div>
<form
name="contactform"
className="contactform"
onSubmit={this.contactSubmit.bind(this)}
>
<div className="col-md-6">
<fieldset>
<input
ref="name"
type="text"
size="30"
placeholder="Name"
onChange={this.handleChange.bind(this, "name")}
value={this.state.fields["name"]}
/>
<span style=\{\{ color: "red" }}>{this.state.errors["name"]}</span>
<br />
<input
refs="email"
type="text"
size="30"
placeholder="Email"
onChange={this.handleChange.bind(this, "email")}
value={this.state.fields["email"]}
/>
<span style=\{\{ color: "red" }}>{this.state.errors["email"]}</span>
<br />
<input
refs="phone"
type="text"
size="30"
placeholder="Phone"
onChange={this.handleChange.bind(this, "phone")}
value={this.state.fields["phone"]}
/>
<br />
<input
refs="address"
type="text"
size="30"
placeholder="Address"
onChange={this.handleChange.bind(this, "address")}
value={this.state.fields["address"]}
/>
<br />
</fieldset>
</div>
</form>
</div>
);
}
}


React.render(<Test />, document.getElementById("container"));
    



In this example I did the validation only for email and name, but you have an idea how to do it. For the rest you can do it self.

There is maybe a better way, but you will get the idea.

Here is fiddle.

Hope this helps.

I've taken your code and adapted it with library react-form-with-constraints: https://codepen.io/tkrotoff/pen/LLraZp

const {
FormWithConstraints,
FieldFeedbacks,
FieldFeedback
} = ReactFormWithConstraints;


class Form extends React.Component {
handleChange = e => {
this.form.validateFields(e.target);
}


contactSubmit = e => {
e.preventDefault();


this.form.validateFields();


if (!this.form.isValid()) {
console.log('form is invalid: do not submit');
} else {
console.log('form is valid: submit');
}
}


render() {
return (
<FormWithConstraints
ref={form => this.form = form}
onSubmit={this.contactSubmit}
noValidate>


<div className="col-md-6">
<input name="name" size="30" placeholder="Name"
required onChange={this.handleChange}
className="form-control" />
<FieldFeedbacks for="name">
<FieldFeedback when="*" />
</FieldFeedbacks>


<input type="email" name="email" size="30" placeholder="Email"
required onChange={this.handleChange}
className="form-control" />
<FieldFeedbacks for="email">
<FieldFeedback when="*" />
</FieldFeedbacks>


<input name="phone" size="30" placeholder="Phone"
required onChange={this.handleChange}
className="form-control" />
<FieldFeedbacks for="phone">
<FieldFeedback when="*" />
</FieldFeedbacks>


<input name="address" size="30" placeholder="Address"
required onChange={this.handleChange}
className="form-control" />
<FieldFeedbacks for="address">
<FieldFeedback when="*" />
</FieldFeedbacks>
</div>


<div className="col-md-6">
<textarea name="comments" cols="40" rows="20" placeholder="Message"
required minLength={5} maxLength={50}
onChange={this.handleChange}
className="form-control" />
<FieldFeedbacks for="comments">
<FieldFeedback when="*" />
</FieldFeedbacks>
</div>


<div className="col-md-12">
<button className="btn btn-lg btn-primary">Send Message</button>
</div>
</FormWithConstraints>
);
}
}

Screenshot:

form validation screenshot

This is a quick hack. For a proper demo, check https://github.com/tkrotoff/react-form-with-constraints#examples

Might be late to answer - if you don't want to modify your current code a lot and still be able to have similar validation code all over your project, you may try this one too - https://github.com/vishalvisd/react-validator.

import React from 'react';
import {sendFormData} from '../services/';


class Signup extends React.Component{
constructor(props){
super(props);
this.state = {
isDisabled:true
}
this.submitForm = this.submitForm.bind(this);
}
validateEmail(email){
const pattern = /[a-zA-Z0-9]+[\.]?([a-zA-Z0-9]+)?[\@][a-z]{3,9}[\.][a-z]{2,5}/g;
const result = pattern.test(email);
if(result===true){
this.setState({
emailError:false,
email:email
})
} else{
this.setState({
emailError:true
})
}
}
handleChange(e){
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
if(e.target.name==='firstname'){
if(e.target.value==='' || e.target.value===null ){
this.setState({
firstnameError:true
})
} else {
this.setState({
firstnameError:false,
firstName:e.target.value
})
}
}
if(e.target.name==='lastname'){
if(e.target.value==='' || e.target.value===null){
this.setState({
lastnameError:true
})
} else {
this.setState({
lastnameError:false,
lastName:e.target.value
})
}
}
if(e.target.name==='email'){
this.validateEmail(e.target.value);
}
if(e.target.name==='password'){
if(e.target.value==='' || e.target.value===null){
this.setState({
passwordError:true
})
} else {
this.setState({
passwordError:false,
password:e.target.value
})
}
}
if(this.state.firstnameError===false && this.state.lastnameError===false &&
this.state.emailError===false && this.state.passwordError===false){
this.setState({
isDisabled:false
})
}
}
submitForm(e){
e.preventDefault();
const data = {
firstName: this.state.firstName,
lastName: this.state.lastName,
email: this.state.email,
password: this.state.password
}
sendFormData(data).then(res=>{
if(res.status===200){
alert(res.data);
this.props.history.push('/');
}else{


}
});
}
render(){
return(
<div className="container">
<div className="card card-login mx-auto mt-5">
<div className="card-header">Register here</div>
<div className="card-body">
<form id="signup-form">
<div className="form-group">
<div className="form-label-group">
<input type="text" id="firstname" name="firstname" className="form-control" placeholder="Enter firstname" onChange={(e)=>{this.handleChange(e)}} />
<label htmlFor="firstname">firstname</label>
{this.state.firstnameError ? <span style=\{\{color: "red"}}>Please Enter some value</span> : ''}
</div>
</div>
<div className="form-group">
<div className="form-label-group">
<input type="text" id="lastname" name="lastname" className="form-control" placeholder="Enter lastname" onChange={(e)=>{this.handleChange(e)}} />
<label htmlFor="lastname">lastname</label>
{this.state.lastnameError ? <span style=\{\{color: "red"}}>Please Enter some value</span> : ''}
</div>
</div>
<div className="form-group">
<div className="form-label-group">
<input type="email" id="email" name="email" className="form-control" placeholder="Enter your email" onChange={(e)=>{this.handleChange(e)}} />
<label htmlFor="email">email</label>
{this.state.emailError ? <span style=\{\{color: "red"}}>Please Enter valid email address</span> : ''}
</div>
</div>
<div className="form-group">
<div className="form-label-group">
<input type="password" id="password" name="password" className="form-control" placeholder="Password" onChange={(e)=>{this.handleChange(e)}} />
<label htmlFor="password">Password</label>
{this.state.passwordError ? <span style=\{\{color: "red"}}>Please enter some   value</span> : ''}
</div>
</div>
<button className="btn btn-primary btn-block" disabled={this.state.isDisabled} onClick={this.submitForm}>Signup</button>
</form>
</div>
</div>
</div>
);
}
}
export default Signup;

With React Hook, form is made super easy (React Hook Form: https://github.com/bluebill1049/react-hook-form)

i have reused your html markup.

import React from "react";
import useForm from 'react-hook-form';


function Test() {
const { useForm, register } = useForm();
const contactSubmit = data => {
console.log(data);
};


return (
<form name="contactform" onSubmit={contactSubmit}>
<div className="col-md-6">
<fieldset>
<input name="name" type="text" size="30" placeholder="Name" ref={register} />
<br />
<input name="email" type="text" size="30" placeholder="Email" ref={register} />
<br />
<input name="phone" type="text" size="30" placeholder="Phone" ref={register} />
<br />
<input name="address" type="text" size="30" placeholder="Address" ref={register} />
<br />
</fieldset>
</div>
<div className="col-md-6">
<fieldset>
<textarea name="message" cols="40" rows="20" className="comments" placeholder="Message" ref={register} />
</fieldset>
</div>
<div className="col-md-12">
<fieldset>
<button className="btn btn-lg pro" id="submit" value="Submit">
Send Message
</button>
</fieldset>
</div>
</form>
);
}

Cleaner way is to use joi-browser package. In the state you should have errors object that includes all the errors in the form. Initially it shoud be set to an empty object. Create schema;

import Joi from "joi-browser";
schema = {
username: Joi.string()
.required()
.label("Username")
.email(),
password: Joi.string()
.required()
.label("Password")
.min(8)
.regex(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{8,1024}$/) //special/number/capital
};

Then validate the form with the schema:

validate = () => {
const options = { abortEarly: false };
const result = Joi.validate(this.state.data, this.schema, options);
console.log(data) // always analyze your data
if (!result.error) return null;
const errors = {};
for (let item of result.error.details) errors[item.path[0]] = item.message; //in details array, there are 2 properties,path and message.path is the name of the input, message is the error message for that input.
return errors;
};

Before submitting the form, check the form:

handleSubmit = e => {
e.preventDefault();
const errors = this.validate(); //will return an object
console.log(errors);
this.setState({ errors: errors || {} }); //in line 9 if we return {}, we dont need {} here
if (errors) return;
//so we dont need to call the server
alert("success");
//if there is no error call the server
this.dosubmit();
};

We have plenty of options to validate the react js forms. Maybe the npm packages have some own limitations. Based up on your needs you can choose the right validator packages. I would like to recommend some, those are listed below.

If anybody knows a better solution than this, please put it on the comment section for other people references.

Try powerform-react . It is based upon powerform which is a super portable Javascript form library. Once learnt, it can be used in any framework. It works even with vanilla Javascript.

Checkout this simple form that uses powerform-react

There is also a complex example.

Try this, example, the required property in the below input tag will ensure that the name field shouldn't be submitted empty.

<input type="text" placeholder="Your Name" required />

Assuming you know about react useState Hook, If your form is simple, you can use state variables to hold the value of each input field. Then add onChange handler function on each input field which will update state variables. At the end, you can check the values stored in state variables to ensure that all the input fields had some value. Here is a simple example.

import { useState } from "react";


export default function App() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const onChangeHandler = (fieldName, value)=>{
if(fieldName === "name"){
setName(value);
}
else if(fieldName==="email"){
setEmail(value);
}
}
const onSubmitHandler = (e)=>{
e.preventDefault();
if(name.trim()==="" || email.trim() ==""){
alert("required both field");
}
else{
alert(name+" " +email);
setName("");
setEmail("");
}
}
return (
<div className="App">
<form onSubmit={(e)=>{onSubmitHandler(e)}}>
<input type="text" value={name} onChange={(e)=>{ onChangeHandler("name",e.target.value)}} /> <br/>
<input type="email"  value={email} onChange={(e)=>{ onChangeHandler("email",e.target.value)}} /> <br/>
<input type="submit" value="Submit" />
</form>
</div>
);
}

However, if you are having a complex form, it's hard to keep each value in state variables and then use validation on each field. For complex forms, it is recommended to use Formik that will do everything for you and you can use Yup validation package which is also supported by Formik that will allow you to add more than just simple validation.

Try this validation plugin in your form where you can add your custom validation rules.

Create a component FormValidation.js

  import { useState } from "react";


const FormValidation = ({ validationRules, formInput }) => {
const [errors, setErrors] = useState(null);


const validation = () => {
// define a empty object to store errors.
let allErrors = {};
// Run loop on validation object
Object.keys(validationRules).forEach((name) => {
// name is the name of input field
const rulesArr = validationRules[name];


// Run loop on validation array applied on that input
rulesArr.forEach((rule) => {
// Skip if any error message is already stored in allErrors object
if (!allErrors[name]) {
let result;
// If rule is an array than it is a type of a function with parameter
switch (Array.isArray(rule)) {
case true: {
// take the function name and parameter value from rule array
const [functionName, paramValue] = rule;
// call validation function


result = functionName(formInput, name, paramValue);
break;
}


default:
// call validation function
result = rule(formInput, name);
break;
}
if (result) {
// append error in object
allErrors = { ...allErrors, ...result };
}
}
});
});


return allErrors;
};


const validate = () =>
new Promise((resolve, reject) => {
const errorObj = validation();


if (Object.keys(errorObj).length === 0) {
setErrors({});
resolve("Success");
} else {
setErrors(errorObj);
reject(Error("Some Error Occurred"));
}
});


return { validate, errors, setErrors };
};


export const required = (formInputs, inputName) =>
!formInputs[inputName] && { [inputName]: "This field is required" };


function emailPattern(email) {
return String(email)
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
}


export const email = (formInputs, inputName) =>
!emailPattern(formInputs[inputName]) && {
[inputName]: "Please enter valid email",
};


export function passwordPattern(formInputs, inputName) {
const value = formInputs[inputName];


let error;
if (value.length < 8) {
error = "Your password must be at least 8 characters";
}
if (value.search(/[a-z]/i) < 0) {
error = "Your password must contain at least one letter.";
}
if (value.search(/[0-9]/) < 0) {
error = "Your password must contain at least one digit.";
}


if (value.search(/[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/) < 0) {
error = "Your password must contain at least one special character.";
}


return (
error && {
[inputName]: error,
}
);
}


export const maxLength = (formInputs, inputName, paramValue) =>
formInputs[inputName].length > paramValue && {
[inputName]: `Maximum characters are ${paramValue}`,
};


export const minLength = (formInputs, inputName, paramValue) =>
formInputs[inputName].length < paramValue && {
[inputName]: `Minimum characters are ${paramValue}`,
};


export default FormValidation;

I want to implement validations in my Login.js

    import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";
import FormValidation, {
required,
email,
passwordPattern,
} from "utils/FormValidation";


const Login = () => {
const [formInput, setFormInput] = useState({
email: "",
password: "",
});


const { validate, errors, setErrors } = FormValidation({
validationRules: {
email: [required, email],
password: [required, passwordPattern],
},
formInput,
});


const handleChange = (e) => {
setFormInput({ ...formInput, [e.target.name]: e.target.value });
};


const handleSubmit = (e) => {
e.preventDefault();
validate().then(() => {
//do whatever you want
console.log(formInput);
// you can set server error manually
setErrors({ email: "Email already exist" });
});
};


return (
<section className="gradient-form" style=\{\{ backgroundColor: "#eee" }}>
<div className="container py-5 h-100">
<div className="row d-flex justify-content-center align-items-center h-100">
<div className="col-xl-10">
<div className="card rounded-3 text-black">
<div className="row g-0">
<div className="col-lg-6">
<div className="card-body p-md-5 mx-md-4">
<Form onSubmit={handleSubmit}>
<p>Please login to your account</p>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Control
type="text"
name="email"
placeholder="Enter email"
onChange={handleChange}
/>
{errors?.email && <span>{errors.email}</span>}
</Form.Group>


<Form.Group
className="mb-3"
controlId="formBasicPassword"
>
<Form.Control
type="password"
name="password"
placeholder="Password"
onChange={handleChange}
/>
{errors?.password && <span>{errors.password}</span>}
</Form.Group>
<Form.Group
className="mb-3"
controlId="formBasicCheckbox"
>
<Form.Check type="checkbox" label="Check me out" />
</Form.Group>
<div className="d-grid gap-2 mb-3">
<Button
variant="primary"
type="submit"
className="gradient-custom-2"
size="md"
>
Submit
</Button>
</div>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default Login;

Now you can pass any custom function in your validation array :

const { validate, errors, setErrors } = FormValidation({
validationRules: {
email: [required, email],
password: [required, passwordPattern, customFunciton],
},
formInput,
});


const customFunciton = (formInputs, inputName) => ({
[inputName]: `This error is from my custom function`,
});

2022
React suggests 3 approaches to handle forms:

  1. Controlled components - In HTML, form elements such as <input>, <textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with setState(). We can combine the two by making the React state be the “single source of truth”. Then the React component that renders a form also controls what happens in that form on subsequent user input. An input form element whose value is controlled by React in this way is called a “controlled component”.

  2. Uncontrolled components - It can sometimes be tedious to use controlled components, because you need to write an event handler for every way your data can change and pipe all of the input state through a React component. This can become particularly annoying when you are converting a preexisting codebase to React, or integrating a React application with a non-React library. In these situations, you might want to check out uncontrolled components, an alternative technique for implementing input forms.

  3. Fully-Fledged Solutions - If you’re looking for a complete solution including validation, keeping track of the visited fields, and handling form submission, Formik is one of the popular choices. However, it is built on the same principles of controlled components and managing state — so don’t neglect to learn them.

All approaches are valid to react hooks too.
First consider which component better suit your needs, and use its appropriate validation solution.