I have a formik form with a select field; two options. When i use onClick I always get "yes" submitted and if i use onChange it does not work in that it does not allow me to choose anything, just always leaves the field the same as before.
I have read a ton of different things. I have tried the setFieldValue, and I have tried onBlur, I have tried numerous different ways in writing the onChange handler without any success. Maybe i am just not changing and writing it properly.
I have looked over the suggested questions already on here and for whatever reason i can not get them to work in my code.
import React, { useState, useRef } from 'react';
import { Form, Field, } from 'formik';
import emailjs from '#emailjs/browser';
const RsvpForm = ({ errors, touched, isValid, dirty }) => {
const form = useRef();
const sendEmail = (e) => {
e.preventDefault();
const userName = e.target[0].value;
const email = e.target[1].value;
const attending = state;
const plusOne = plusone;
const guests = e.target[4].value;
const guestNumber = e.target[5].value;
const guest_name = e.target[6].value;
const song = e.target[7].value;
const message = e.target[8].value;
let templateParams = {
userName: userName,
email: email,
attending: attending,
plusOne: plusOne,
guests: guests,
guestNumber: guestNumber,
guest_name: guest_name,
song: song,
message: message,
};
emailjs.send(process.env.REACT_APP_SERVICE_ID, process.env.REACT_APP_TEMPLATE_ID, templateParams, process.env.REACT_APP_PUBLIC_KEY)
.then((result) => {
console.log(result.text);
e.target.reset();
}, (error) => {
console.log(error.text);
});
};
const[state, attendingState] = useState("");
const onClick = (e) => {
let{value} = e.target;
if(value=== 'yes') {
attendingState('yes')
}else {
attendingState('no')
}
}
const[plusone, plusOnestate] = useState("");
const onPick = (e) => {
let{value} = e.target;
if(value=== 'no') {
plusOnestate('no')
}else {
plusOnestate('yes')
}
}
return (
<div className='form-group'>
<label className='col-form-label'>Plus One:</label>
<Field
component='select'
className={
touched.plusOne
? `form-control ${errors.plusOne ? 'invalid' : 'valid'}`
: `form-control`
}
name='plusOne'
type='select'
// onChange={(e) => setFieldValue('plusOne', e.target.value)}
onClick={onPick}
>
<option value="">Please select an answer</option>
<option value="yes">Yes, please add a plus one or few</option>
<option value="no">Just me!</option>
</Field>
{touched.plusOne && errors.plusOne && (
<small className='text-warning'><strong>{errors.plusOne}</strong></small>
)}
</div>
Related
const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
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 handleSubmit = (e) => {
setFormErrors(validate(formValues));
setIsSubmit(true);
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
);
I'm trying to use axios to do POST method for creating user.
I got everything works fine but there's one small problem but I don't know how to fix.
The problem is that I always need to submit the form 2 times to make the POST request. There's nothing happen in the first submit, but it will work in the second submit.
Does anyone know what's wrong with my code?
Edited
According to #DBS solution.
I'm trying to follow the steps but now the form can't submit anymore. Can someone let me know if I missed something?
const [name, setName] = useState("");
const [age, setAge] = useState("");
const initialValues = {
name: "",
age: "",
};
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
if (!Object.keys(formErrors).length && !isSubmitting) {
setFormErrors(validate(formValues));
} else {
userCreate();
setisSubmitting(true);
}
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name is required";
}
if (!values.age) {
errors.age= "Age is required";
}
return errors;
};
const userCreate = async () => {
await api.post("/createuser", {
name,
age,
});
};
return (
<div class="container">
<Form
onSubmit={handleSubmit}
>
<Form.Field>
<label>Name</label>
<input
name="name"
onChange={(e) => {
setName(e.target.value);
handleChange(e);
}}
values={formValues.name}
/>
<span className="error-message">{formErrors.name}</span>
</Form.Field>
<Form.Field>
<label>Age</label>
<input
name="age"
onChange={(e) => {
setAge(e.target.value);
handleChange(e);
}}
values={formValues.age}
/>
<p className="error-message">{formErrors.age}</p>
<
The issue here is your isSubmit, it is required to be true for userCreate to be called:
onSubmit={
Object.keys(formErrors).length === 0 && isSubmit
? userCreate
: handleSubmit
}
But it starts as false:
const [isSubmit, setIsSubmit] = useState(false);
And is only ever updated when handleSubmit is called (which, confusingly, is only called when the validation fails)
So your current code does this:
isSubmit is false
Submit is clicked, handleSubmit is called and isSubmit is set to true
Submit is clicked again, now isSubmit is true it will call userCreate
To solve this, there are a few different approaches, but I would:
Move all the submit handler logic into onSubmit={handleSubmit} (To keep things clear)
Inside there, do your error length check (0 error) and isSubmit (Which I would probably rename to isSubmitting for clarity, and make sure it's false) (E.g. !Object.keys(formErrors).length && !isSubmitting)
If there are errors, show the appropriate message (Leaving isSubmitting as false)
If not, call userCreate (And set isSubmitting to true)
Lastly, if this can be submitted multiple times, add an effect/callback/then to reset isSubmitting once the call is complete.
Are you using the isSubmitting flag for something? if not below might be work for you.
If there is no error, calling the create method
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
userCreate();
}
};
if isSubmitting is used to check the submit or create in progress
const handleSubmit = (e) => {
setFormErrors(validate(formValues));
if(Object.keys(formErrors).length === 0) {
setIsSubmitting(true);
userCreate();
}
};
The flag isSubmitting should be set as false on API is success or failed
setIsSubmitting(false)
I am pretty new to React, and the only CRUD functionality I have done has been in MVC.net, so I am pretty lost here and none of the tutorials I am finding seem to be the situation I have going on...or maybe I am just misunderstanding them. The tips I got with my initial question helped, but I am still no getting it to work, so hopefully now I have supplies enough info to help other help me. I did no include all of the input fields because their are like 15 and it was just redundant.
I am pulling up the modal using this onClick event:
onClick={()=> {handleEditModal(item)}}
modalFunctions.js
// Modal Functionality
export function ModalFunctions() {
const [selectedRecord, setSelectedRecord] = useState([]);
const [openModal, setOpenModal] = useState(false);
const handleEditModal = item =>{
setOpenModal(true)
setSelectedRecord(item)
}
return {
selectedRecord,
setSelectedRecord,
openModal,
setOpenModal,
handleEditModal,
handleDetailModal
}
}
// Form Functionality
export function FormFunctions(validateOnChange = false, validate) {
const [values, setValues] = useState('');
const [errors, setErrors] = useState({});
const handleInputChange = e => {
const { name, value } = e.target
setValues({
...values,
[name]: value
})
if (validateOnChange)
validate({ [name]: value })
}
return {errors, handleInputChange, setErrors, setValues, values}
}
DataTable.js
// Imported Modal Functions
const {
selectedRecord,
openModal,
setOpenModal,
handleEditModal,
handleDetailModal
} = ModalFunctions();
const baseURL = 'http://localhost:8080/api/tanks';
// Fetch Data
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get(baseURL);
setTableData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
};
fetchData();
}, [baseURL, setTableData, setLoading]);
// The Modal
return(
<Modal
title={ "Editing: " + (selectedRecord.tankName) }
openModal={openModal}
setOpenModal={setOpenModal}
>
<TankEdit
selectedRecord={selectedRecord}
setOpenModal={setOpenModal}
openModal={openModal}
/>
</Modal>
)
TankEdit.js
export function TankEdit(props) {
const { baseURL, openModal, selectedRecord, setOpenModal, setTableData } = props;
const validate = (fieldValues = item) => {
let temp = { ...errors }
if ('tankName' in fieldValues)
temp.tankName = fieldValues.tankName ? "" : "This field is required."
setErrors({
...temp
})
if (fieldValues === values)
return Object.values(temp).every(x => x === " ")
}
const {
values,
setValues,
errors,
setErrors,
handleInputChange,
} = FormFunctions(true, validate);
useEffect(() => {
if (selectedRecord !== null)
setValues({
...selectedRecord
})
}, [selectedRecord, setValues])
function editRecord() {
axios.put(`${baseURL}`, {
title: "Success",
body: "The record had been successfully updated"
}).then ((response) => {setTableData(response.data);})
}
const handleSubmit = e => {
e.preventDefault()
if (validate()) {
editRecord(values);
}
setOpenModal(false)
}
const item = values; // used for easier referencing (matches table item)
return (
<Form onSubmit={handleSubmit} open={openModal}>
<Grid>
<Controls.Input
name="tankName"
label="Tank Name"
value={item.tankName}
onChange={handleInputChange}
error={errors.tankName}
/>
</Grid>
<Grid>
<Controls.Button
type="submit"
text="Submit"
/>
<Controls.Button
text="Cancel"
color="default"
onClick={()=>{setOpenModal(false)}}
/>
</Grid>
</Form>
)
}
Input.js
export default function Input(props) {
const { error=null, label, name, onChange, value, ...other } = props;
return (
<TextField
variant="outlined"
label={label}
name={name}
value={value}
defaultValue=''
onChange={onChange}
{...other}
{...(error && {error:true,helperText:error})}
/>
)
}
My company is only wanting a Read and an Update function, since Creating and Deletion will be handled another ways, so this is my final hangup. I think I am close, but I am missing something.
Can anyone point me in the right direction?
THANKS!!!!
If you want to write an update request you would use axios.put to send the data to your back-end.
In your handleSubmit function you do:
let response = await axios.put('http://-------/api/tanks', { name: 'tank' });
(The second parameter is an object that needs to contain all the form data fields)
Also make sure you call e.preventDefault() in the handleSubmit function so you don't accidentally navigate elsewhere.
Then you will update your database or whatever using your back-end.
for update you should use put or patch method
and you should send id of item you want to update in request url.
I insert 2 example here.
this is for put:
const res = await axios.put('/api/article/123', {
title: 'Making PUT Requests with Axios',
status: 'published'
});
this is for patch:
const res = await axios.patch('/api/article/123', {
title: 'Making PUT Requests with Axios',
status: 'published'
});
I'm creating a contact form, and attempting to setState for each field, then pass that value to an email form.
When I console.log, I'm not getting any output. What am I doing wrong here?
Should I be using useEffect? From what I understand, useEffect is called whenever I set state, so I shouldn't need to. Is there something else I'm missing or doing wrong?
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
setName(document.getElementById('name').value)
setEmail(document.getElementById('email').value)
setSubject(document.getElementById('subject').value)
setMessage(document.getElementById('message').value)
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
Set state is an asynchronous operation, so log the state after setting it, will log the old value.
If you want to sure that the value has been setten correctly, you need to use setState.
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
setShowSuccessMessage(prev => {
console.log(prev)
return prev
})// log the 'Contact form submitted'
setShowFailureMessage(prev => {
console.log(prev)
return prev
})// log false
And setting state, in this case, will not make the component re-render as you return the same value.
The reason is that setXxx methods are async. If you click submit 10 times fast enough, you will see different results.
You have two options:
not preferred: If you don't use 'controlled' input (you are not using it now - in input elements, you are not using things like 'value={name}'). then you don't need the state at all.
Use controlled input. You can check document on reactjs org.
Simply put,
useState
on the input tag, add value={name} onChange={changeHandler},
in changHandler, setState
in formSubmit, use the value in state
There is a section on forms on this excellent tutorial:
Bob Ziroll free React Course
You are using react so you need to set data and use those data after that i have updated your code it should work for you. i.e. you need to set value in onChange function as below:-
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
const style = {};
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' onChange={(e) => setName(e.target.value)} />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' onChange={(e) => setEmail(e.target.value)} />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' onChange={(e) => setSubject(e.target.value)} />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
onChange={(e) => setMessage(e.target.value)}
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
And if you want same for phone you can also set data for the same and use after that.
I'm using React and Yup to validate an email address.
My schema looks like this:
const registrationSchema = yup.object().shape({
email: yup.string().email().required()
})
And my code like this:
const handleOnSubmit = async () => {
const formData = {
email: props.emailInput
}
const isValid = await registrationSchema.isValid(formData)
setInputErrorState(isValid ? false : true)
}
The code above is doing the validation, but if I input a non-ASCII character like a Japanese or Chinese character it doesn't work. For example: ハロー#ハロー.com is passing the validation.
How can I allow only ASCII or romanji characters in my validation with YUP?
Ideal scenario:
attack.on.titan#gmail.com ✅
black#clover.com ✅
ハロー#ハロー.com ❌
Try this out using Regex
const isEmail = email => {
const re = /^(([^<>()\[\]\\.,;:\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,}))$/;
if (re.test(String(email).toLowerCase())) {
return email;
} else {
return false;
}
};
I removed yup and I used the answer suggested by Ibrahim shamma.
Here is the solution (RFC5322 compliant):
utils.tsx
// Regex taken from https://emailregex.com/
export const isEmail = email => {
const re = /^(([^<>()\[\]\\.,;:\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,}))$/;
if (re.test(String(email).toLowerCase())) {
return true;
}
return false;
};
Component/index.tsx
import React from 'react';
import {isEmail} from '../utils'
export interface ComponentInterface {
emailInput:string
setEmailInput: (e: any) => void
}
const Component : React.FC<ComponentInterface> = (props:ComponentInterface) => {
const [inputErrorState, setInputErrorState] = useState(false)
const handleOnChange = (e) => {
if (inputErrorState) {
setInputErrorState(false)
}
props.setEmailInput(e.target.value)
}
const handleOnSubmit = () => {
const isValid = isEmail(props.emailInput)
setInputErrorState(isValid ? false : true)
}
return ( <div>
<input
type='email'
id='props.emailInput'
value={emailInput}
onChange={handleOnChange}
></input>
{inputErrorState && (
<p>Your error message</p>
)}
</div> );
}
export default Component;
I have 3 <select> components. In the first component I have the days, in the second the months and in the third the years. These components act as one date picker. I tried to wrap with Formik's connect and use setFieldValue and setFieldError, but the error disappears after I edit an other component in that form.
How can I solve it to the error remain persistent. As I saw, this behaviour is known.
class DateField extends Component {
constructor(props) {
super(props);
const value = props.value;
this.state = {
day: moment.utc(value).date(),
month: moment.utc(value).month(),
year: moment.utc(value).year(),
};
}
handleChange = e => {
const stateProp = e.target.name.split('.').pop();
this.setState(
{
[stateProp]: e.target.value,
},
() => {
const { formik } = this.props;
const date = this.dateFromState();
const error = this.props.validate(date);
formik.setFieldValue(this.props.name, date, false);
formik.setFieldError(this.props.name, error);
}
);
};
handleBlur = () => {
const { formik } = this.props;
const date = this.dateFromState();
const error = this.props.validate(date);
formik.setFieldTouched(this.props.name, true, false);
formik.setFieldError(this.props.name, error);
};
dateFromState() {
// returns date constructed from the state
}
render() {
const { formik, name } = this.props;
const error = getValueByPath(formik.errors, name);
return (
<div className="form-group">
<label>{this.props.label}</label>
<select
name={`${this.props.name}.day`}
value={this.state.day}
onChange={this.handleChange}
onBlur={this.handleBlur}
>
{
// options for days
}
</select>
<select
name={`${this.props.name}.month`}
value={this.state.month}
onChange={this.handleChange}
onBlur={this.handleBlur}
>
{
// options for months
}
</select>
<select
name={`${this.props.name}.year`}
value={this.state.year}
onChange={this.handleChange}
onBlur={this.handleBlur}
>
{
// options for years
}
</select>
{error && (
<div className="validation-message">
<span>{error}</span>
</div>
)}
</div>
);
}
}
const getValueByPath = (obj, path) => path.split('.').reduce((o, i) => o && o[i], obj);
export default connect(DateField);
My advice would be in de-coupling the component from formik, let it provide a date and handle change accordingly. the form in which you use this can then determine if the provided date is valid or not and pass the error to your controlled component to show until the date is valid. This is also great for re-usability.
const DatePicker = ({ name, value, error, onChange, onBlur }) => {
const today = new Date(value);
const [day, setDay] = useState(today.getUTCDay());
const [month, setMonth] = useState(today.getUTCMonth());
const [year, setYear] = useState(today.getUTCYear());
useEffect() => {
const newDate = new Date([year, month, day]);
const target = { name, value: newDate };
onChange({ target });
}, [day, month, year]);
return (
//do your JSX here;
);
}