Send mail on validation React - javascript

I'm trying to make a simple form that has validation and after the validation is success send a mail with Email.js. I'm using only email.js. I'm trying to set an if statement that would check that my formErrors object is empty and if it is to "fire" the mail sending. But so far I've tried a few methods but they all send mail even if the form hasn't passed the validation (formErrors object has value). My code here (I removed my service, template and key from .sendForm) with the latest check I attempted (Object.keys(formErrors).length === 0) that still doesn't prevent the mail to be sent:
import React, { useState, useRef } from "react";
import emailjs from "#emailjs/browser";
import "./App.css";
function App() {
const initialValues = { user: "", email: "", pass: "" };
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const form = useRef();
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
setIsSubmit(true);
if (Object.keys(formErrors).length === 0) {
emailjs
.sendForm(
"",
"",
form.current,
""
)
.then(
(result) => {
console.log(result.text);
},
(error) => {
console.log(error.text);
}
);
}
};
const validate = (v) => {
const errors = {};
const regex = /^[^\s#]+#[^\s#]+\.[^\s#]{2,}$/i;
if (!v.user) {
errors.user = "This field can't be empty";
}
if (!v.email) {
errors.email = "This field can't be empty";
} else if (!regex.test(v.email)) {
errors.email = "Incorrect email";
}
if (!v.pass) {
errors.pass = "This field can't be empty";
} else if (v.pass.length < 6) {
errors.pass = "Password needs to be at least 6 characters";
}
return errors;
};
return (
<div className="container">
{Object.keys(formErrors).length === 0 && isSubmit ? (
<div className="title">Success</div>
) : (
<div className="title">Fill the form</div>
)}
<form ref={form} onSubmit={handleSubmit}>
<h1>Login Form</h1>
<hr />
<div className="form">
<div className="field">
<label>Username</label>
<input
type="text"
name="user"
placeholder="Username"
value={formValues.user}
onChange={handleChange}
/>
</div>
<p>{formErrors.user}</p>
<div className="field">
<label>Email</label>
<input
type="text"
name="email"
placeholder="Email"
value={formValues.email}
onChange={handleChange}
/>
</div>
<p>{formErrors.email}</p>
<div className="field">
<label>Password</label>
<input
type="password"
name="pass"
placeholder="Password"
value={formValues.pass}
onChange={handleChange}
/>
</div>
<p>{formErrors.pass}</p>
<button className="btn">Submit</button>
</div>
</form>
</div>
);
}
export default App;

I guess your issue related with non async behaviour of states. In handleSubmit function you set formErrors and right after you are checking it in conditional statement. If you replace your
if (Object.keys(formErrors).length === 0)
with
if (Object.keys(validate(formValues)).length === 0)
it should work as expected.
Another solution would be adding this in the beginning of handleSubmit function and it will not enter to any other logic of the submit
if (Object.keys(validate(formValues)).length > 0) {
return
}

Related

Having Issues with Form Validation for a SignUp component

I have a Sign Up Component in my current project, and I'm trying to implement validation for the email and phone number.
Code:
export default function Form() {
// States for registration
const [firstname, setFirstName] = useState('');
const [lastname, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phonenumber, setPhoneNumber] = useState('');
// States for checking the errors
const [submitted, setSubmitted] = useState(false);
const [error, setError] = useState(false);
// Handling the email change
const handleEmail = (e) => {
setEmail(e.target.value);
setSubmitted(false);
};
// Handling the phonenumber change
const handlePhoneNumber = (e) => {
setPhoneNumber(e.target.value);
setSubmitted(false);
};
// Handling the form submission
const handleSubmit = (e) => {
e.preventDefault();
if (email === '' || phonenumber === '') {
setError(true);
} else {
setSubmitted(true);
setError(false);
}
};
// Showing error message if error is true
const errorMessage = () => {
return (
<div
className="error"
style={{
display: error ? '' : 'none',
}}>
<h1>Please enter all the fields</h1>
</div>
);
};
return (
<div className="form">
<div className="messages">
{errorMessage()}
{successMessage()}
</div>
<div className='inputval'>
<div className="d-flex justify-content-center flex-column">
<label className="label">Email</label>
<input onChange={handleEmail} className="input"
value={email} type="email" />
<label className="label">Phone Number</label>
<input onChange={handlePhoneNumber} className="input"
value={phonenumber} type="email" />
</div>
<div className="d-inline-block justify-content-center align-items-center">
<button className="btn" onClick={handleSubmit} type="submit">
Submit
</button>
</div>
</div>
</div>
);
}
For the most part, I tried implementing /^(([^<>()[\]\.,;:\s#\"]+(\.[^<>()[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i for the format constant in my email but I had no luck. I have a useState hook that checks if the boxes are empty, but if I could get some assistance on this, it would be much appreciated!
There are a lot of form validation npm tools that will help alot. But if you want to do everything custom and understand about how it will work, here is a quick project demonstrating how to go about it. I would recommend putting some of the helper functions in different files so they can be used everywhere in your app. CodeSandbox: https://codesandbox.io/s/simple-form-validation-jqfvpy?file=/src/Input.js:0-325
export default function App() {
const [form, setForm] = useState({ name: "", email: "", phone: "" });
const [errors, setErrors] = useState({ name: [], email: [], phone: [] });
const checkRules = (input, rules) => {
let errors = [];
let value = input;
if (typeof value === "string") value = input.trim();
if (rules.required) {
if (value === "") errors.push("*This field is required.");
}
if (rules.phone) {
let phoneno = new RegExp(/^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/);
if (!phoneno.test(value))
errors.push("*Please Enter valid phone number XXX-XXX-XXXX");
}
if (rules.email) {
let pattern = new RegExp(
/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(#\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i
);
if (!pattern.test(value)) errors.push("*Please enter a valid email.");
}
return errors;
};
const checkFormValidation = (f) => {
const errors = {};
errors.name = checkRules(f.name, { required: true });
errors.phone = checkRules(f.phone, { phone: true });
errors.email = checkRules(f.email, { email: true });
for (const [, value] of Object.entries(errors)) {
if (value.length > 0) return { noErrors: false, errors };
}
return { noErrors: true, errors };
};
const handleSubmit = (f) => {
const { errors, noErrors } = checkFormValidation(f);
setErrors(errors);
if (noErrors) {
alert(JSON.stringify(f));
}
};
return (
<div className="App">
<div style={{ display: "grid", placeItems: "center" }}>
<Input
name="Name"
value={form.name}
errors={errors.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<Input
name="Email"
value={form.email}
errors={errors.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
<Input
name="Phone"
value={form.phone}
errors={errors.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
/>
<button onClick={() => handleSubmit(form)}>Submit</button>
</div>
</div>
);
}
export const Input = ({ name, value, onChange, errors }) => {
return (
<>
<input type="text" placeholder={name} value={value} onChange={onChange} />
{errors.length > 0
? errors.map((e) => (
<p style={{ fontSize: "9px", color: "red" }}>{e}</p>
))
: null}
</>
);
};

where to call the Axios post request in reactjs

I have a form,thats data are saved in the state to be sent to the backend server.
i am handling the form with handleSubmit function and useEffect hook, where the handleSubmit prevents the form from being submitted unless it calls the validation function, in the useEffect I check if there are any errors using if condition and then console.log my data.
now I want to post the data hold in the state -the state is sent as a props to me- but I am confused whether to put the request in the HandleSubmit function or in the useEffect inside the body of the if condition.
import react, { Component, useState, useEffect } from 'react';
import {useNavigate } from 'react-router-dom';
import axios from 'axios';
import './sign.css';
const SignA = (props) => {
const navigate = useNavigate();
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleSubmit = (err) => {
err.preventDefault();
setFormErrors(validate(props.data));
setIsSubmit(true);
}
useEffect(() => {
console.log(Object.keys(formErrors).length);
if (Object.keys(formErrors).length === 0 && isSubmit) {
console.log('console the props data', props.data)
//here is where I think the post request should be put
if (isSubmit) {
return (navigate('/profileadmin'))
}
}
}, [formErrors])
const validate = (values) => {
const errors = {};
const regex = /^[^\s#]+#[^\s#]+\.[^\s#]{2,}$/i;
if (!values.firstname) {
errors.firstname = 'firstname is required!';
}
if (!values.lastname) {
errors.lastname = 'lastname is required!';
}
if (!values.mobile) {
errors.mobile = 'mobile is required!';
}
if (!values.email) {
errors.email = 'email is required!';
} else if (!regex.test(values.email)) {
errors.email = 'this is not a valid email format!'
}
return errors;
}
return (
<div className='signup'>
<form onSubmit={handleSubmit} >
<div className="container">
<h1>Sign Up</h1>
<div className="name">
<div>
<input
type="text"
placeholder="First name"
name="firstname"
id='firstName'
value={props.data.firstname}
onChange={props.change}
/>
</div>
<div>
<input
type="text"
placeholder="Last name"
name="lastname"
value={props.data.lastname}
onChange={props.change}
/>
</div>
</div>
<p className='errorMsg'>{formErrors.firstname}</p>
<p className='errorMsg'>{formErrors.lastname}</p>
<br />
<div>
<input
type="text"
placeholder="Business mobile number"
name="mobile"
value={props.data.mobile}
onChange={props.change}
/>
<p className='errorMsg'>{formErrors.mobile}</p>
<br />
<input
type="text"
placeholder="Email Adress"
name="email"
value={props.data.email}
onChange={props.change}
/>
<p className='errorMsg'>{formErrors.email}</p>
<br />
</div>
</div>
<br />
<div className="checkbox">
<label>
<input type="checkbox" className="check" />i’ve read and agree with <a href="url" >Terms of service</a>
</label>
</div>
<div className="clearfix">
<button type="submit" className="signupbtn">Sign Up</button>
</div>
</div>
</form >
</div >
)
}
export default SignA;
this is the request
axios.post('', props.data)
.then(res => console.log('post res', res))
.catch(error => {
console.error('There was an error in post request!', error);
});
You don't necessarily need useEffect here.
Here is how you can implement such thing:
Declare a state to hold form values:
const [formData, setFormData] = useState({})
Declare function to set the state:
const handleChange = (name, value) => {
setFormData({...formData, [name]: value})
}
Input onChange to capture:
// handleChange has two parameters
<input
type="text"
placeholder="First name"
name="firstname"
id='firstName'
value={props.data.firstname}
onChange={(event) => handleChange('firstName', event.target.value)}
/>
function for calling post axios post request
const handleSubmit = () => {
//check for validations code here
// if validations are right then post request here
// this will give you all the fields like firstName: "", lastName: ""
let requestBody = {
...formData
}
axios.post("url", requestBody).then((res)=> {
//your code here
})
}

React useState hook: object property remains unchanged

I have recently started learning react and I have a component that has a login form:
import Joi from "joi";
import { useState } from "react";
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const loginSchema = Joi.object().keys({
username: Joi.string().required(),
password: Joi.string().required(),
});
const [errors, setErrors] = useState({ username: null, password: null });
const handleSubmit = (form) => {
form.preventDefault();
validate();
};
const validate = () => {
const { error } = loginSchema.validate(
{ username, password },
{ abortEarly: false }
);
if (!error) return null;
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
setErrors({ ...errors, [key]: value });
}
return errors;
};
return (
<div className="container">
<div>
<h1>Login</h1>
</div>
<div className="card">
<div className="card-body">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="username">Username</label>
<input
name="username"
type="text"
className="form-control"
id="username"
placeholder="Enter username"
value={username}
onChange={(username) => setUsername(username.target.value)}
/>
{errors.username && (
<div className="alert alert-danger">{errors.username}</div>
)}
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
name="password"
type="password"
className="form-control"
id="password"
placeholder="Password"
value={password}
onChange={(password) => setPassword(password.target.value)}
/>
{errors.password && (
<div className="alert alert-danger">{errors.password}</div>
)}
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
{/* Delete later */}
<h4>{JSON.stringify(errors)}</h4>
</div>
</div>
</div>
);
};
export default Login;
Here, I am trying to set the values of username and password in the errors object, but for some reason, only the password property gets set, the username property remains null. I have used object destructuring but it still doesn't set the value, please help.
State updates are not reflected immediately in React, and since you loop over your data to set the errors object multiple times, it only sets in the last key due to batching in event handlers and the fact that state updates are affected by closures.
You should instead create an updated objected and set it in state once
let newErrors = {};
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
newErrors = { ...newErrors, [key]: value };
}
setErrors(prev => ({...prev, ...newErrors}));
I think the problem comes from this code
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
setErrors({ ...errors, [key]: value });
}
Here you are calling setErrors in a for loop with the same errors object so only the last field is taken into account.
Here is a possible solution :
if (!error) return null;
const newErrors = error.details.reduce((acc, item) => {
const key = item.path[0];
const value = item.message;
acc[key] = value;
return acc;
}, {...errors});
setErrors(newErrors);

React: How to change form validation from onSubmit validation for all fields, to onBlur instant validation for each field in the form?

I have the form which is functioning great, but I need to make the validation instant (as soon as the user defocuses the field, I want the error message shown for that field only). At the moment I have a validation that shows error messages when the submit button is clicked:
Here is the code:
The useForm hook
import { useState, useEffect } from 'react';
const useForm = (callback, validate, post) => {
const [values, setValues] = useState(post || {});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
}, [errors]);
const handleSubmit = (event) => {
if (event) event.preventDefault();
console.log('values in useForm', values)
setErrors(validate(values))
setIsSubmitting(true);
};
const handleInputChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleInputChange,
handleSubmit,
values,
errors,
}
};
export default useForm;
The validate function
const validate = (values) => {
const errors = {};
if (!values.title) {
errors.title = 'Title is required'
} else if (values.title.length < 5) {
errors.title = 'Title must be at least 5 characters long'
}
if (!values.body) {
errors.body = "Blog body is required"
} else if (values.body.length < 2 || values.body.length > 20) {
errors.body = "Text has to be between 2 and 20 characters long"
}
if (!values.author) {
errors.author = "The author's name is required"
}
if (!values.number) {
errors.number = "A number is required"
}
if (!values.email) {
errors.email = 'Email is required';
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = 'Invalid email address';
}
return errors;
}
export default validate;
The form component
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import useForm from '../hooks/useForm';
import { addPost } from '../actions';
import validate from '../helpers/validate';
const CreateForm = () => {
const dispatch = useDispatch();
const history = useHistory();
const { values, handleInputChange, handleSubmit, errors } = useForm(submit, validate)
const { title, body, author, number, email } = values;
function submit() {
console.log("No errors", values)
const post = values;
console.log('Submit form', post)
dispatch(addPost(post))
history.push('/');
}
return (
<div className="create">
<h2>Add a new blog</h2>
<form onSubmit={handleSubmit} noValidate>
<label>Blog title:</label>
<input
type="text"
required
name="title"
value={title || ""}
onChange={handleInputChange}
className={errors.title ? 'red-border' : ""}
/>
{errors.title && (<p className="danger">{errors.title}</p>)}
<label>Blog body:</label>
<textarea
required
name="body"
value={body || ""}
onChange={handleInputChange}
className={errors.body ? 'red-border' : ""}
/>
{errors.body && (
<p className="danger">{errors.body}</p>
)}
<label>Author:</label>
<input
type="text"
required
name="author"
value={author || ""}
onChange={handleInputChange}
className={errors.author ? 'red-border' : ""}
/>
{errors.author && (
<p className="danger">{errors.author}</p>
)}
<label>Number:</label>
<input
type="number"
required
name="number"
value={number || ""}
onChange={handleInputChange}
className={errors.number ? 'red-border' : ""}
/>
{errors.number && (
<p className="danger">{errors.number}</p>
)}
<label>Email:</label>
<input
type="text"
required
name="email"
value={email || ""}
onChange={handleInputChange}
className={errors.email ? 'red-border' : ""}
/>
{errors.email && (
<p className="danger">{errors.email}</p>
)}
<button>Save</button>
</form>
</div>
);
}
export default CreateForm;
As dicussed in the comments, you just need to call your setError when you want to update your error helpers. Here's a live example that flags an error if you type "error" in any of the fields: https://codesandbox.io/s/wispy-monad-3t8us?file=/src/App.js
const validateInputs = (e) => {
console.log("validating inputs");
if (e.target.value === "error")
setErrors({ ...errors, [e.target.name]: true });
else setErrors({ ...errors, [e.target.name]: false });
};
<input
type="text"
required
name="title"
value={values.title}
onChange={handleInputChange}
style={errors.title ? { border: "2px solid red" } : null}
onBlur={validateInputs}
/>
As you said in your question, you should call validate() in onBlur instead of in onSubmit.
So just add onBlur event in each of your input:
<input
type="number"
required
name="number"
onBlur={validate}
onChange={handleInputChange}
/>

Using preventDefault in React.js

The validation is working correctly, however when the user fails the validation the default action is still carried out. So validation fails and the joke is still returned. If validation fails, the Joke should not be returned. I have tried to use preventDefault but no luck.
import React, { useState } from "react";
function Search() {
const [joke, setJoke] = useState()
const [firstname, setFN] = useState("sharoze")
const [lastname, setLN] = useState("khan")
const newJoke = (first, last) => {
fetch(`http://api.icndb.com/jokes/random?firstName=${first}&lastName=${last}`)
.then(result => result.json())
.then(result2 => {
console.log(result2)
setJoke(result2.value.joke)
})
}
function validateForm() {
var firstname = document.getElementsByName("firstname")[0].value;
var lastname = document.getElementsByName("lastname")[0].value;
if (firstname === "" && lastname === "") {
alert("Please enter atleast one name");
}
else if (!(/^[a-zA-Z]+$/.test(firstname + lastname))) {
alert("'Only alphabets allowed'");
}
}
return (
<div className="jokeForm" >
<form name="searchForm" >
<input type="text" name="firstname" placeholder="First name" value={firstname} onChange={(e) => setFN(e.target.value)} />
<input type="text" name="lastname" placeholder="Last name" value={lastname} onChange={(e) => setLN(e.target.value)} />
</form>
<button id="button" onClick={(e) => { validateForm(newJoke(firstname, lastname)); return false; }}>click here for a personalised chuckle</button>
<h3>{joke}</h3>
</div >
)
}
export default Search;
sorry the code has been a little butchered!
The main problem I can see is that you don't pass event argument from onClick to your validation function, hence preventDefault() function is not working.
Try to pass event argument to validation function and then use this argument in function to apply preventDefault().
--Update--
You don't need to use preventDefault() at all. Just modify your validation function to call new joke only if validation is passed.
import React, { useState } from "react";
function Search() {
const [joke, setJoke] = useState()
const [firstname, setFN] = useState("sharoze")
const [lastname, setLN] = useState("khan")
const newJoke = (first, last) => {
fetch(`http://api.icndb.com/jokes/random?firstName=${first}&lastName=${last}`)
.then(result => result.json())
.then(result2 => {
console.log(result2)
setJoke(result2.value.joke)
})
}
function validateForm() {
var firstname = document.getElementsByName("firstname")[0].value;
var lastname = document.getElementsByName("lastname")[0].value;
if (firstname === "" && lastname === "") {
alert("Please enter atleast one name");
return false;
}
else if (!(/^[a-zA-Z]+$/.test(firstname + lastname))) {
alert("'Only alphabets allowed'");
return false;
}
newJoke(firstname, lastname);
}
return (
<div className="jokeForm" >
<form name="searchForm" >
<input type="text" name="firstname" placeholder="First name" value={firstname} onChange={(e) => setFN(e.target.value)} />
<input type="text" name="lastname" placeholder="Last name" value={lastname} onChange={(e) => setLN(e.target.value)} />
</form>
<button id="button" onClick={() => validateForm()}>click here for a personalised chuckle</button>
<h3>{joke}</h3>
</div >
)
}
export default Search;

Categories

Resources