i am using number type here so that it accepts only number.But i don't want this number type like it's gives increments and decrements selector at the right side of the field.What i need is i need to accept only numbers not any characters.How can i do that?
these are my form elements:
<label className="w3-label w3-text-blue w3-large">
Phone Number:
<input type="number" className="w3-input w3-border" value={this.state.phoneno}
onChange={e => this.setState({ phoneno: e.target.value })} required placeholder="Enter Father's Phone No. in Numbers"/>
</label><br/>
<label className="w3-label w3-text-blue w3-large">
Mobile Number:
<input type="number" className="w3-input w3-border" value={this.state.mobileno} pattern="[0-9]{10}" title="Please enter 10 digit mobile Number"
onChange={e => this.setState({ mobileno: e.target.value })} required placeholder="Enter Father's Mobile No. in Numbers"/>
</label></div><br/>
Try this
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
https://css-tricks.com/snippets/css/turn-off-number-input-spinners/
You can use regular expressions in javascript to check whether given number is valid or not.
function phonenumber(inputtxt)
{
var phoneno = /^\d{10}$/;
if((inputtxt.value.match(phoneno))
{
return true;
}
else
{
alert("message");
return false;
}
}
This kind of tasks is where React really shines. Just use <input>s with type="text" and let your onChange handlers do the job. I would suggest using String.replace with \D (not a digit) regexp to filter out unwanted chars, and then String.substr to control the length of the entered value. Something like:
const filterNonDigits = value => value ? value.replace(/\D+/, '') : '';
const takeLast = (amount, value) => value ? value.substr(0, amount) : '';
class NumbersInput extends React.Component {
constructor() {
super();
this.state = {};
}
handlePhone(value) {
this.setState({ phoneno: filterNonDigits(value) });
}
handleMobile(value) {
this.setState({ mobileno: takeLast(10, filterNonDigits(value)) });
}
render() {
return (
<div>
<label>
Phone Number:
<input
value={this.state.phoneno}
onChange={e => this.handlePhone(e.target.value)}
/>
</label>
<br/>
<label>
Mobile Number:
<input
value={this.state.mobileno}
onChange={e => this.handleMobile(e.target.value)}
/>
</label>
</div>
);
}
}
Related
Here is my code. The confirm state variable on line 34 will not change. Calling setConfirm from handleSubmit does not change it. I've attempted deliberately toggling it in dev tools, no effect. I've tried deliberately setting it to true in the code, and placing {confirm && <h1>TEST</h1>} at the top of the contact form. It doesn't render the h1, even when deliberately set to true. But {true && <h1>TEST</h1>} works just fine. It's as if the confirm variable doesn't even exist. Can someone shed some light on why this might be happening?
import { useState, useEffect } from 'react'
import styles from "./ContactAndBooking.module.css"
const ContactAndBooking = () => {
// Contact form values
const [contact, setContact] = useState({
name: null,
email: null,
phone: null,
reason: null,
message: null
})
// Booking form values
const [booking, setBooking] = useState({
name: null,
email: null,
phone: null,
date: null,
time: null,
description: null
})
// Store today's date, calendar selection minimum
const [minDate, setMinDate] = useState()
// Display contact form or booking form, switch on button press
const [typeContact, setTypeContact] = useState("contact")
// Flag invalid form values for validation alerts
const [validFlag, setValidFlag] = useState(true)
// Successful message confirmation flag
const [confirm, setConfirm] = useState(false)
// Keep track of today's date, booking calendar selection minimum
useEffect(()=> {
let today = new Date
let dd = today.getDate()
let mm = today.getMonth() + 1
let yyyy = today.getFullYear()
if (dd < 10)
dd = '0' + dd
if (mm < 10)
mm = '0' + mm
today = yyyy + '-' + mm + '-' + dd
setMinDate(today)
})
// Validate form values
const validate = () => {
// Track and return form validity, switch to false if any field fails
let isValid = true
// Check validity of contact form values
if (typeContact === "contact"){
// If no name, trigger invalid flag
if (!contact.name){
isValid = false
}
// If no email or email does not match regex, trigger invalid flag
if (!contact.email || !contact.email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)){
isValid = false
}
// If phone number length is neither 10 nor 11 digits long, trigger invalid flag
if (!contact.phone || ![10,11].includes(contact.phone.split('-').join('').split('').length) || contact.phone.split('-').join('').split('').some(e => !'0123456789'.includes(e))){
isValid = false
}
// If no reason for contact is given, trigger invalid flag
if (!contact.reason){
isValid = false
}
// If message field is blank, trigger invalid flag
if (!contact.message){
isValid = false
}
}
// Check validity of booking form values
else if (typeContact === "booking"){
// If no name, trigger invalid flag
if (!booking.name){
isValid = false
}
// If no email or email does not match regex, trigger invalid flag
if (!booking.email || !booking.email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)){
isValid = false
}
// If phone number length is neither 10 nor 11 digits long, trigger invalid flag
if (!booking.phone || ![10,11].includes(booking.phone.split('-').join('').split('').length)|| booking.phone.split('-').join('').split('').some(e => !'0123456789'.includes(e))){
isValid = false
}
// If no booking date is selected, trigger invalid flag
if (!booking.date){
isValid = false
}
// If no booking time is selected, trigger invalid flag
if (!booking.time){
isValid = false
}
// If no booking description is given, trigger invalid flag
if (!booking.description){
isValid = false
}
}
// set form validation alert flag on validation failure
!isValid ? setValidFlag(false) : setValidFlag(true)
return isValid
}
const handleSubmit = (e) => {
e.preventDefault()
setValidFlag(true)
if (validate()){
console.log("CONFIRMATION TRIGGERING...") // TESTING
// Trigger confirmation window
setConfirm(true) // NOT TRIGGERING
console.log("CONFIRMATION TRIGGERED") // TESTING
if (typeContact === "contact"){
// To be replaced with Axios request
console.log(contact)
}
else if (typeContact === "booking"){
// To be replaced with Axios request
console.log(booking)
}
}
// set form validation alert flag on validation failure
else {
setValidFlag(false)
console.log("SUBMISSION INVALID")
}
}
// Contact form view
const contactWindow = () => {
return (
<form onSubmit={e => handleSubmit(e)}>
{confirm && <h1>TEST</h1>}
<label className={styles.label} htmlFor="name">Name: </label>
<input
className={styles.input}
name="name"
type="text"
onChange={e => setContact({...contact, name: e.target.value})}
/>
<span className={!validFlag && !contact.name ? styles.warning : styles.warningHidden}>Please enter your name.</span>
<label className={styles.label} htmlFor="email">Email: </label>
<input
className={styles.input}
name="email"
type="email"
onChange={e => setContact({...contact, email: e.target.value})}
/>
<span className={
!validFlag && (!contact.email || !contact.email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/))
? styles.warning
: styles.warningHidden
}>
Please enter a valid email address.
</span>
<label className={styles.label} htmlFor="phone">Phone: </label>
<input
className={styles.input}
name="phone"
type="text"
onChange={e => setContact({...contact, phone: e.target.value})}
/>
<span className={
!validFlag && (!contact.phone || ![10,11].includes(contact.phone.split('-').join('').split('').length) || contact.phone.split('-').join('').split('').some(e => !'0123456789'.includes(e)))
? styles.warning
: styles.warningHidden
}>Please enter a valid phone number.</span>}
<select
className={styles.select}
onChange={e => setContact({...contact, reason: e.target.value})}
defaultValue="Reason for contact"
>
<option className={styles.option} value="Reason for contact" disabled>Reason for contact</option>
<option className={styles.option} value="Business Inquiry">Business Inquiry</option>
<option className={styles.option} value="Ticket Sales">Ticket Sales</option>
<option className={styles.option} value="Other">Other</option>
</select>
<span className={!validFlag && !contact.reason ? styles.warning : styles.warningHidden}>Please select a reason for contact.</span>
<textarea
className={`${styles.textarea} ${styles.textareaContact}`}
placeholder="How can I help you?"
onChange={e => setContact({...contact, message: e.target.value})}
/>
<span className={!validFlag && !contact.message ? styles.warning : styles.warningHideen}>Please enter your message.</span>
<button className={styles.submit} role="submit">Submit</button>
</form>
)
}
// Booking form view
const bookingWindow = () => {
return (
<form onSubmit={handleSubmit}>
<label className={styles.label} htmlFor="name">Name: </label>
<input
className={styles.input}
name="name"
type="text"
onChange={e => setBooking({...booking, name: e.target.value})}
/>
<span className={!validFlag && !booking.name ? styles.warning : styles.warningHidden}>Please enter your name.</span>
<label className={styles.label} htmlFor="email">Email: </label>
<input
className={styles.input}
name="email"
type="email"
onChange={e => setBooking({...booking, email: e.target.value})}
/>
<span className={
!validFlag && (!booking.email || !booking.email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/))
? styles.warning
: styles.warningHidden
}>
Please enter a valid email address.
</span>
<label className={styles.label} htmlFor="phone">Phone: </label>
<input
className={styles.input}
name="phone"
type="text"
onChange={e => setBooking({...booking, phone: e.target.value})}
/>
<span className={
!validFlag && (!booking.phone || ![10,11].includes(booking.phone.split('-').join('').split('').length)|| booking.phone.split('-').join('').split('').some(e => !'0123456789'.includes(e)))
? styles.warning
: styles.warningHidden
}>
Please enter a valid phone number.
</span>
<label className={styles.label} htmlFor="date">Select Date: </label>
<input
className={styles.datetime}
type="date"
name="date"
min={minDate}
onChange={e => setBooking({...booking, date: e.target.value})}
/>
<span className={!validFlag && !booking.date ? styles.warning : styles.warningHidden}>Please select a date for your event.</span>
<label className={styles.label} htmlFor="time">Select Time: </label>
<input
className={styles.datetime}
type="time"
name="time"
onChange={e => setBooking({...booking, time: e.target.value})}
/>
<span className={!validFlag && !booking.time ? styles.warning : styles.warningHidden}>Please select a time for your event.</span>
<textarea
className={`${styles.textarea} ${styles.textareaBooking}`}
placeholder="Please give a brief description of your event."
onChange={e => setBooking({...booking, description: e.target.value})}
/>
<span className={!validFlag && !booking.description ? styles.warning : styles.warningHidden}>Please describe your event.</span>
<button className={styles.submit} role="submit">Submit</button>
</form>
)
}
// Message confirmation window, triggered on successful validation and submission
const confirmWindow = () => {
return (
<div className={styles.confirmContainer}>
<h1 className={`${styles.confirmText} ${styles.confirmThankYou}`}>Thanks for the message!</h1>
<h3 className={`${styles.confirmText} ${styles.confirmSubThankYou}`}>I'll get back to you ASAP.</h3>
<button className={styles.confirmOkay} onClick={setConfirm(false)}>Okay</button>
</div>
)
}
return (
<div className={styles.container} id="contact">
<button
role="button"
className={`${styles.toggle} ${styles.toggleContact}`}
onClick={() => {
setTypeContact("contact")
setValidFlag(true)
}}
>
CONTACT
</button>
<button
role="button"
className={`${styles.toggle} ${styles.toggleBooking}`}
onClick={() => {
setTypeContact("booking")
setValidFlag(true)
}}
>
BOOKING
</button>
<br />
{typeContact === "contact" && contactWindow()}
{typeContact === "booking" && bookingWindow()}
{confirm && confirmWindow()}
</div>
)
}
export default ContactAndBooking
When you call setConfirm(false) in your onClick event, what you are actually saying is to run the setConfirm state update as soon as the page loads. What you want to do is create an arrow function in the onClick so that you are passing reference to a function that will be ran once the button is clicked.
<button className={styles.confirmOkay} onClick={() => setConfirm(false)}>Okay</button>
If you wanted to do more than just call setConfirm, you can create another function and call setConfirm inside of that, then you can just pass the new function as reference so that every time the button is clicked the setConfirm function gets called.
const handleConfirmUpdate = () => {
// do some stuff in here if you'd like
setConfirm(true);
}
<button className={styles.confirmOkay} onClick={handleConfirmUpdate}>Okay</button>
Notice that we are not calling the function, but rather passing a reference.
try changing line 273 from
<button className={styles.confirmOkay} onClick={setConfirm(false)}>Okay</button>
to
<button className={styles.confirmOkay} onClick={() => setConfirm(false)}>Okay</button>
then it appears to be working
there is required section in my form also I want to send successfully text after click of submit button. But problem is here when I click submit button, it shows successfully text no matter form is correct or not. Can you help me about that ? I am beginner :)
My react code here
import React, { Component } from "react";
export default class Form extends Component {
state = {
name: "",
surname: "",
phone: "",
email: "",
comments: "",
// isValid: true,
succesfully: ""
};
/* preventSending = async (e) => {
await this.setState({ [e.target.name]: e.target.value });
if (
this.state.name === "" ||
this.state.surname === "" ||
this.state.phone === "" ||
this.state.email === "" ||
this.state.comments === ""
) {
this.setState({ isValid: true });
} else {
this.setState({ isValid: false });
}
};*/
handleSubmit = (e) => {
this.setState({
succesfully: `${this.state.name} you have sent successfully `
});
};
render() {
return (
<div className="">
<form>
<label htmlFor="name">
Name :
<input
onChange={this.preventSending}
id="name"
name="name"
type="text"
required
/>
</label>
<br />
<label htmlFor="surname">
Surname :
<input
onChange={this.preventSending}
id="surname"
name="surname"
type="text"
required
/>
</label>
<br />
<label htmlFor="phone">
Phone :
<input
onChange={this.preventSending}
id="phone"
name="phone"
type="tel"
required
/>
</label>
<br />
<label htmlFor="email">
Email :
<input
onChange={this.preventSending}
id="email"
name="email"
type="email"
required
/>
</label>
<br />
<label htmlFor="textArea">
Comments :
<textarea
onChange={this.preventSending}
id="textarea"
name="comments"
required
/>
</label>
<br />
<button
type="submit"
// disabled={this.state.isValid}
onClick={this.handleSubmit}
>
Send details{" "}
</button>
</form>
<p>{this.state.succesfully}</p>
</div>
);
}
}
It says I have to add more details but I explained everything clear.
I have to go through your code and try to resolve the errors and get the proper output.
I see that you take the direct state object and update its value, I just corrected that part and also add one error flag in it, so that you can display one informational error message while you click the button without adding the data.
Apart from that, in your form, you take one button which has submit type.
As of now I simply update it with type=button, as type=submit will submit the form and redirect us to new URL.
Please let me know if it is useful to you or not.
here is my code
import React, { Component } from "react";
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
surname: "",
phone: "",
email: "",
comments: "",
// isValid: true,
succesfully: "",
error: "",
};
}
/* preventSending = async (e) => {
await this.setState({ [e.target.name]: e.target.value });
if (
this.state.name === "" ||
this.state.surname === "" ||
this.state.phone === "" ||
this.state.email === "" ||
this.state.comments === ""
) {
this.setState({ isValid: true });
} else {
this.setState({ isValid: false });
}
};*/
handleChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value,
});
}
handleSubmit = (e) => {
if (
this.state.name !== "" &&
this.state.surname !== "" &&
this.state.phone !== "" &&
this.state.email !== "" &&
this.state.comments !== ""
) {
// check valid email or not with regex
const regexp = /^([\w\.\+]{1,})([^\W])(#)([\w]{1,})(\.[\w]{1,})+$/;
let isValidEmail = regexp.test(this.state.email) ? true : false;
if (isValidEmail) {
this.setState({
succesfully: `${this.state.name} you have sent successfully `,
error: "",
});
} else {
this.setState({
succesfully: "",
error: "Please add proper email",
});
}
} else {
this.setState({
succesfully: "",
error: "Please add proper data",
});
}
};
render() {
return (
<div className="">
<form>
<label htmlFor="name">
Name :
<input
onChange={(e) => this.handleChange(e)}
id="name"
name="name"
type="text"
value={this.state.name}
required
/>
</label>
<br />
<label htmlFor="surname">
Surname :
<input
onChange={(e) => this.handleChange(e)}
id="surname"
name="surname"
type="text"
value={this.state.surname}
required
/>
</label>
<br />
<label htmlFor="phone">
Phone :
<input
onChange={(e) => this.handleChange(e)}
id="phone"
name="phone"
type="tel"
value={this.state.phone}
required
/>
</label>
<br />
<label htmlFor="email">
Email :
<input
onChange={(e) => this.handleChange(e)}
id="email"
name="email"
type="email"
value={this.state.email}
required
/>
</label>
<br />
<label htmlFor="textArea">
Comments :
<textarea
onChange={(e) => this.handleChange(e)}
id="textarea"
name="comments"
value={this.state.comments}
required
/>
</label>
<br />
<button
// type="submit" // use this while you want to submit your form
type="button" // I use button to call handleSubmit method and display message
// disabled={this.state.isValid}
onClick={this.handleSubmit}
>
Send details
</button>
</form>
<p>{this.state.succesfully}</p>
<p>{this.state.error}</p>
</div>
);
}
}
I see that the function prevent sending that you created should work except you misplaced the isValid argument:
function ValidateEmail(email)
{
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email))
{
return true
}
return false
}
if (
this.state.name === "" ||
this.state.surname === "" ||
this.state.phone === "" ||
this.state.email === "" ||
this.state.comments === "" ||
!validateEmail(this.state.email)
) {
this.setState({ isValid: false });
} else {
this.setState({ isValid: true });
}
also uncomment disabled and isValid which should be false at the begginning:
// disabled={this.state.isValid}
// isValid = false
I'm using vuelidate to validate a form in VUE build. I have a phone number input that needs to be formatted as 222-222-2222 which I have setup using a phone mask. My issue is now the input will not validate when the phone is filled in. My error message is still displayed. Is there a way to allow for numbers only along with a dash "-"???
<div class="form-field">
<label for="phone">
Phone Number
</label>
<input type="text" class="form-input text-input" name="phone" id="phone" #input="phoneNumberMask()"
v-model.trim="$v.formData.phone.$model">
<label v-if="($v.formData.phone.$dirty && !$v.formData.phone.required)
|| ($v.formData.phone.$dirty && !$v.formData.phone.numeric)
|| ($v.formData.phone.$dirty && !$v.formData.phone.minLength)
|| ($v.formData.phone.$dirty && !$v.formData.phone.maxLength)" class="error">
Please enter a valid phone number.
</label>
</div>
validations: {
formData: {
phone: {
required,
numeric,
minLength: minLength(12),
maxLength: maxLength(12)
}
}
}
methods: {
phoneNumberMask(){
const x = this.formData.phone.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
this.formData.phone = !x[2] ? x[1] : x[1] + '-' + x[2] + (x[3] ? '-' + x[3] : '');
},
}
As pointed out in the comments, you can create a regular expression that validates you phone number format, in this case /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/ could be a starting point.
Here a complete example.
<template>
<div class="">
<label
:class="{
error: $v.number.$error,
green: $v.number.$dirty && !$v.number.$error
}"
>
<input
class=""
v-model="number"
#change="$v.number.$touch()"
placeholder="Phone number"
/>
</label>
<div class="" v-if="$v.number.$dirty && $v.number.$error">
<p class="">NOT VALID</p>
</div>
<button
class="
"
:disabled="$v.$invalid"
#click="saveClick()"
>
<span class="">Save</span>
</button>
</div>
</template>
Script part
import { required, helpers } from "vuelidate/lib/validators"
const number = helpers.regex(
"serial",
/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/
)
export default {
data() {
return {
number: null
}
},
computed: {
serial: {
get() {
return this.number
},
set(value) {
this.number = value
}
}
},
methods: {
saveClick() {
//TODO
}
},
validations: {
number: {
required,
number
}
}
}
Note
This is for Vue 2.x and vuelidate ^0.7.6
The whole idea is to take users input in a form and display their input in a JSON object. In state I have an array and inside it another array.
My Form.js looks like this,
state= {
groups:[{
typeA:[{}],
typeB:[{}]
}],
credentials: false
};
change = e =>{
this.setState({[e.target.name]: e.target.value})
};
handleSubmit(event) {
event.preventDefault();
this.setState({
credentials: true
});
}
render(){
return(
<div class="classform">
<form >
<label>
Field1:
<br/>
<input type="text"
name="typeA"
placeholder="Type A"
//store it as the first element of the type A
value={this.state.groups.typeA[0]}
onChange={this.change.bind(this)}
/>
//other fields with the same code
Subsequently, Field2 will be stored as the second element of type A
Then, Field3 and Field4 will be stored as 2 of type B array
I expect the code to give me an output like :
"typeA": ["field1 value", "field2 value"],
"typeB": ["field3 value", "field4 value"]}
I'm a beginner with React and I'm not able to store the field values in the state which is an array.
For the sake of simplicity, I will recommend below solution,
Instead of having a nested array in the state, you can manage the input values in the different state variables and at the time of submitting, change them to the output you want.
state = {
field1: '',
field2: '',
field3: '',
field4: '',
}
change = e => {
this.setState({[e.target.name]: e.target.value})
};
handleSubmit(event) {
event.preventDefault();
const { field1, field2, field3, field4 } = this.state;
// This is your output
const output = [{typeA: [field1, field2], typeB: [field2, field3]}];
this.setState({
credentials: true
});
}
render(){
return(
<div class="classform">
<form >
<label>
Field1:
<br/>
<input type="text"
name="field1"
placeholder="Type A"
value={this.state.field1}
onChange={this.change}
/>
</label>
<label>
Field2:
<br/>
<input type="text"
name="field2"
placeholder="Type A"
value={this.state.field2}
onChange={this.change}
/>
</label>
try this:
import React, { Component } from "react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
groups: [
{
typeA: [{}],
typeB: [{}]
}
],
credentials: false
};
}
change = (e, key) => {
let { groups } = this.state;
let myVal = groups[0][e.target.name];
// or if you want to add value in an object likr [{value: 'abcd'}]
myVal[0][key] = e.target.value;
groups[0][e.target.name] = myVal;
console.log("TCL: App -> groups", groups);
this.setState({ groups });
};
render() {
return (
<div>
<input
type="text"
name="typeA"
placeholder="Type A"
value={this.state.groups[0].typeA[0].value2}
onChange={e => this.change(e, "value2")}
/>
<input
type="text"
name="typeA"
placeholder="Type A2"
value={this.state.groups[0].typeA[0].value}
onChange={e => this.change(e, "value")}
/>
<br />
<input
type="text"
name="typeB"
placeholder="Type b"
value={this.state.groups[0].typeB[0].value}
onChange={e => this.change(e, "value")}
/>
</div>
);
}
}
Give each input a custom attribute, for example data-group="typeA". In your on change function get that value and add the values to the correct array.
<input
type="text"
name="col2"
placeholder="Type A"
data-group="typeA" // add custom attribute typeA | typeB etc.
onChange={e => this.change(e)}
/>
In your change handle get the custom attribute, and use it to add the value to the correct array.
change = e => {
// create a copy of this.state.groups
const copyGroups = JSON.parse(JSON.stringify(this.state.groups));
// get data-group value
const group = event.target.dataset.group;
if (!copyGroups[0][group]) {
copyGroups[0][group] = []; // add type if it doesn't exists
}
const groups = copyGroups[0][group];
const index = this.findFieldIndex(groups, e.target.name);
if (index < 0) {
// if input doesn't exists add to the array
copyGroups[0][group] = [...groups, { [e.target.name]: e.target.value }];
} else {
// else update the value
copyGroups[0][group][index][e.target.name] = e.target.value;
}
this.setState({ groups: copyGroups });
};
pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
function formatState(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) => match);
}
const Credentials = ({ value }) => {
return <pre>{formatState(value)}</pre>;
};
class App extends React.Component {
state = {
groups: [
{
typeA: [],
typeB: []
}
],
credentials: false
};
handleSubmit = event => {
event.preventDefault();
this.setState({
credentials: true // display Credentials component
});
};
// get the current input index in the array typeA | typeB
findFieldIndex = (array, name) => {
return array.findIndex(item => item[name] !== undefined);
};
change = e => {
// create a copy of this.state.groups
const copyGroups = JSON.parse(JSON.stringify(this.state.groups));
// get data-group value
const group = event.target.dataset.group;
if (!copyGroups[0][group]) {
copyGroups[0][group] = []; // add new type
}
const groups = copyGroups[0][group];
const index = this.findFieldIndex(groups, e.target.name);
if (index < 0) {
// if input doesn't exists add to the array
copyGroups[0][group] = [...groups, { [e.target.name]: e.target.value }];
} else {
// update the value
copyGroups[0][group][index][e.target.name] = e.target.value;
}
this.setState({ groups: copyGroups });
};
removeKey = (key) => {
const temp = {...this.state};
delete temp[key];
return temp;
}
render() {
return (
<div>
<input
type="text"
name="col1"
placeholder="Type A"
data-group="typeA"
onChange={e => this.change(e)}
/>
<input
type="text"
name="col2"
placeholder="Type A"
data-group="typeA"
onChange={e => this.change(e)}
/>
<input
type="text"
name="col2"
placeholder="Type B"
data-group="typeB"
onChange={e => this.change(e)}
/>
<input
type="text"
name="typec"
placeholder="Type C | New Type"
data-group="typeC"
onChange={e => this.change(e)}
/>
<input
type="text"
name="typed"
placeholder="Type D | New Type"
data-group="typeD"
onChange={e => this.change(e)}
/>
<button onClick={this.handleSubmit}>Submit</button>
{this.state.credentials && (
<Credentials value={JSON.stringify(this.removeKey('credentials'), undefined, 2)} />
)}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
I'm trying to create a contact form. The form looks like this:
<form novalidate [formGroup]="contact" (ngSubmit)="send()">
<p>
<label>Name
<br>
<input type="text" class="input" value="" formControlName="name">
<span class="error">Enter your name</span>
</label>
</p>
<p>
<label>E-mail
<br>
<input type="email" class="input" value="" formControlName="email">
<span class="error">It looks like this email is invalid</span>
</label>
</p>
<p>
<label>Phone
<br>
<input type="text" class="input" value="" formControlName="telefone">
<span class="error">It looks like this phone number is invalid</span>
</label>
</p>
<p>
<label>Message
<br>
<textarea type="text" class="input" value="" formControlName="message"></textarea>
<span class="error">The message can't be empty</span>
</label>
</p>
<p class="submit">
<input type="submit" name="submit" class="bt" value="Send">
</p>
</form>
In this form only the message and the name and email or the phone number fields should be required.
I'm using a formBuilder class, so here's the TypeScript code:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.compose([/*Use custom validador??*/])],
phone: ['', Validators.compose([/*Use custom validador??*/]],
message: ['', Validators.required]
});
I tried using custom validators as a solution, but I could not figure out a solution. Any suggestions?
I created a custom validator directive:
import {
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '#angular/forms';
export const atLeastOne = (validator: ValidatorFn, controls:string[] = null) => (
group: FormGroup,
): ValidationErrors | null => {
if(!controls){
controls = Object.keys(group.controls)
}
const hasAtLeastOne = group && group.controls && controls
.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {
atLeastOne: true,
};
};
To use it, you just do this:
this.form = this.formBuilder.group({
name: ['', Validators.required],
email:[''],
telefone:[''],
message:['', Validators.required],
}, { validator: atLeastOne(Validators.required, ['email','telefone']) });
So email or telefone would be required here. If you leave it empty then any control with a value is fine and you can use it with any type of validator, not just Validators.required.
This is reusable in any form.
Yes, a custom validator is the way to go.
Make your form group like this:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required],
phone: ['', Validators.required],
message: ['', Validators.required]
}, {validator: this.customValidationFunction})
Then have the customValidationFunction check for validation. Made up validation just for example:
customValidationFunction(formGroup): any {
let nameField = formGroup.controls['name'].value; //access any of your form fields like this
return (nameField.length < 5) ? { nameLengthFive: true } : null;
}
Change each input like this (changing your p tags to divs. Substitute the control name for each and change syntax for the hidden span tag validation where appropriate):
<div [ngClass]="{'has-error':!contact.controls['name'].valid && contact.controls['name'].touched}">
<label>Name</label>
<input class="input" type="text" [formControl]="contact.controls['name']">
<span [hidden]="!contact.hasError('nameLengthFive')" class="error">Enter your name</span>
</div>
I have updated my validator snippet to support string and number types
I was inspired by Todd Skelton. This is a very simple Validator that does just what you ask for and nothing else:
/**
* Validates if at least one of the provided fields has a value.
* Fields can only be of type number or string.
* #param fields name of the form fields that should be checked
*/
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') return field && field >= 0 ? true : false;
if (typeof field === 'string') return field && field.length > 0 ? true : false;
})
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}
And here is how I use it:
ngOnInit(): void {
this.form = this.formBuilder.group(
{
field: [this.field],
anotherField: [this.anotherField],
},
{ validator: atLeastOne('field','anotherField') },
);
}
Slightly refactored Florian's answer for the lazy as it was making ts-sonar angry (cognitive-complexity rule):
const isFieldEmpty = (fieldName: string, fg: FormGroup) => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') { return field && field >= 0 ? true : false; }
if (typeof field === 'string') { return field && field.length > 0 ? true : false; }
};
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => isFieldEmpty(fieldName, fg))
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}