My formik form is like this
const NewProduct = ({}) => {
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required'
}
return errors
}
const formik = useFormik({
initialValues: {
title: '',
price: '',
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2))
},
})
return (
<div className="newProductComponent">
<form onSubmit={formik.handleSubmit}>
<label>title</label>
<input
type="text"
id="title"
name="title"
onChange={formik.handleChange}
value={formik.values.title}
/>
{formik.errors.title ? (
<div className="error">{formik.errors.title}</div>
) : null}
<NumberFormat
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { formattedValue, value } = values
}}
/>
</form>
</div>
)
}
How can I get number format component in that form to work with formik?
useFormik - the hook you're already using - returns a function setFieldValue that can be used to manually set a value.
First arg is field name price and second is the value. You also must set attribute name="price" on <NumberFormat>.
const App = () => {
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required'
}
return errors
}
const formik = useFormik({
initialValues: {
title: '',
price: '',
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2))
},
})
return (
<div className="newProductComponent">
<form onSubmit={formik.handleSubmit}>
<label>title</label>
<input
type="text"
id="title"
name="title"
onChange={formik.handleChange}
value={formik.values.title}
/>
{formik.errors.title ? (
<div className="error">{formik.errors.title}</div>
) : null}
<br />
<label>Price</label>
<NumberFormat
name="price"
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { value } = values;
formik.setFieldValue('price', value);
}}
/>
<br />
<button type="submit">Submit</button>
</form>
</div>
)
};
Live Demo
You can set the values using useField and useFormikContext
example:
const [field, meta] = useField('price');
const { setFieldValue } = useFormikContext();
const isError = meta.touched && Boolean(meta.error);
<NumberFormat
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { formattedValue, value } = values
setFieldValue(field.name, formattedValue)
}}
/>
{isError && <div className="error">{meta.error}</div>}
Related
I have a simple Formik Form and one from my field is custom field named MySpecialField which i try to implement but there is issue with autofocus behavior. I can only type by one character in my MySpecialField and then autofocus is gone.
I'm new with Formik and can not understand why it happens and how it could be solved?
const formik = useFormik<UserGroupInterface>({
initialValues: {
firstName: "",
groupName: "",
},
validate: (data: UserGroupInterface) => {
let errors: Record<string, string> = {};
if (!data.groupName) {
errors.groupName = "* required";
}
if (!data.firstName) {
errors.firstName = "* required";
}
return errors;
},
onSubmit: (data): void => {
console.log("submit", data);
},
});
const formikTouched: any = formik.touched;
const formikErrors: any = formik.errors;
const isFormFieldValid = (name: any) =>
!!(formikTouched[name] && formikErrors[name]);
const getFormErrorMessage = (name: any) => {
return (
isFormFieldValid(name) && (
<small className="p-error">{formikErrors[name]}</small>
)
);
};
//#ts-ignore
const MySpecialField = ({ field, props }) => {
return <InputText {...field} {...props} />;
};
<FormikProvider value={formik}>
<form
onSubmit={formik.handleSubmit}
className="p-fluid grid formgrid my-5"
>
<div className="field col-12 md:col-4">
<span className="p-float-label">
<Field
name="firstName"
id="firstName"
component={MySpecialField}
className={classNames({
"p-invalid": isFormFieldValid("firstName"),
})}
autoFocus // This is do not work as expected, only by one character
value={formik.values.firstName}
onChange={formik.handleChange}
/>
<label
htmlFor="firstName"
className={classNames({
"p-error": isFormFieldValid("firstName"),
})}
>
Group 1
</label>
</span>
{getFormErrorMessage("firstName")}
</div>
<div>
<span className="p-float-label">
<InputText
id="groupName"
name="groupName"
value={formik.values.groupName}
onChange={formik.handleChange}
autoFocus
className={classNames({
"p-invalid": isFormFieldValid("groupName"),
})}
/>
<label
htmlFor="name"
className={classNames({
"p-error": isFormFieldValid("groupName"),
})}
>
Group 2
</label>
</span>
{getFormErrorMessage("groupName")}
</div>
What I'm trying to achieve, it's to create each input as own component.
And I can't get how to fix thing when my app all the time rerendering when I'm pressing any key.
I know if I'll use controlledInputs -> so each input would have it own useState that would work. But the main idea to do that this way
import React, { useState } from 'react';
const ControlledInputs = () => {
const [person, setPerson] = useState({ firstName: '', email: '', age: '' });
const [people, setPeople] = useState([]);
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setPerson({ ...person, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: '', email: '', age: '' });
}
};
function FormControl({number, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={person[idname]}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
return (
<>
<article className='form'>
<form>
<FormControl idname={"firstName"} type={"text"} text={"First name"}/>
<FormControl type={"email"} idname={"email"} text={"Email"}/>
<FormControl type={"age"} idname={"age"} text={"Age"}/>
<button type='submit' className='btn' onClick={handleSubmit}>
add person
</button>
</form>
</article>
<article>
{people.map((person) => {
const { id, firstName, email, age } = person;
return (
<div key={id} className='item'>
<h4>{firstName}</h4>
<p>{email}</p>
<p>{age}</p>
</div>
);
})}
</article>
</>
);
};
export default ControlledInputs;
You need to define the FormControl outside of ControlledInputs, otherwise, React will recreate it and you lose focus as well as data.
And you need to pass value and handleChange as props in FormControl.
Here are codes you can refactor. Please note that the number you defined is not removed.
function FormControl({value, handleChange, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={value}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
Usage in form:
function FormControl({value, handleChange, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={value}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
const ControlledInputs = () => {
const [person, setPerson] = useState({ firstName: '', email: '', age: '' });
const [people, setPeople] = useState([]);
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setPerson({ ...person, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: '', email: '', age: '' });
}
};
return (
<>
<article className='form'>
<form>
<FormControl idname={"firstName"} type={"text"} text={"First name"} value={person['firstName']} handleChange={handleChange}/>
<FormControl type={"email"} idname={"email"} text={"Email"} value={person['email']} handleChange={handleChange}/>
<FormControl type={"age"} idname={"age"} text={"Age"} value={person['age']} handleChange={handleChange}/>
<button type='submit' className='btn' onClick={handleSubmit}>
add person
</button>
</form>
</article>
<article>
{people.map((person) => {
const { id, firstName, email, age } = person;
return (
<div key={id} className='item'>
<h4>{firstName}</h4>
<p>{email}</p>
<p>{age}</p>
</div>
);
})}
</article>
</>
);
};
That is because FormControl is defined inside the ControlledInputs component. So, on each rerender of the ControlledInputs, you create a new FormControl function and that means that React will treat it as a different component than the one in the previous render, and so it will lose focus, as the old one is considered unmounted.
Just define the FormControl outside the other one, and pass it what extra data you need as props, and you should be set.
I'm looking to implement the form validation using react-hook. However, I'm facing some trouble in doing so as I've also added some stuff on my own under the handleSubmit and I'm not really sure how to go about it.
export default function Contact() {
const [message, setMessage] = useState(false);
const [alert, setAlert] = useState(true);
const { register, errors} = useForm();
const [showElement, setShowElement] = React.useState(false);
const handleSubmit = (e) => {
e.preventDefault();
emailjs.sendForm('', '', e.target, '')
.then((result) => {
console.log(result.text);
}, (error) => {
console.log(error.text);
});
e.target.reset();
setMessage(true);
setShowElement(true);
setTimeout(function () {
setShowElement(false);
}, 4000);
};
const onSubmit= data=>{
console.log(data);
}
return (
<div className="right">
<h2>Contact Me</h2>
<form onSubmit={handleSubmit} id="contactform">
<input type="text" placeholder="Name" name="name" ref={register({required: true, minLength: 2})}
required />
<button type="submit">Send</button>
</form>
{showElement ? (
<div className="submitmsg">
{message && (
<span> Messaged received. I'll respond to your query ASAP! </span>
)}
</div>
) : (
<div> </div>
)}{" "}
</div>
)
}
Thank you!
React hook form provides the handeSubmit method that receives the form data after validations. Also, you must use the errors object to show errors in the UI.
Here is the sandbox link: https://codesandbox.io/s/exciting-dust-df5ft?file=/src/App.js
I have updated your code accordingly:
import { useState } from "react";
import React from "react";
import emailjs from "emailjs-com";
import { useForm } from "react-hook-form";
export default function Contact() {
const [message, setMessage] = useState(false);
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
const [showElement, setShowElement] = React.useState(false);
const onSubmit = (data) => {
emailjs
.send(
"service_t1ccrgq",
"template_gmmcyzr",
data,
"user_d0vUwhmqvbIYhEsyZF8tu"
)
.then(
(result) => {
console.log(result.text);
},
(error) => {
console.log(error.text);
}
);
setMessage(true);
setShowElement(true);
setTimeout(function () {
setShowElement(false);
}, 4000);
};
return (
<div className="contact" id="contact">
<div className="left">
<img className="contactme" src="asset/email.gif" />
</div>
<div className="right">
<h2>Contact Me</h2>
<form onSubmit={handleSubmit(onSubmit)} id="contactform">
<input
type="text"
placeholder="Name"
name="name"
{...register("name", {
required: "Name is Required",
minLength: {
value: 3,
message: "Should be greater than 3 characters"
}
})}
/>
<input
type="tel"
placeholder="Mobile Number"
name="mobile"
{...register("mobile", {
required: "Mobile Number is Required",
minLength: {
value: 3,
message: "Should be greater than 3 characters"
}
})}
/>
<input
type="text"
placeholder="Email"
name="email"
{...register("email", {
required: "Email is Required",
minLength: {
value: 3,
message: "Should be greater than 3 characters"
}
})}
/>
<textarea
placeholder="Message"
required
name="message"
{...register("message", {
required: "Message is Required",
minLength: {
value: 3,
message: "Should be greater than 3 characters"
}
})}
></textarea>
<button type="submit">Send</button>
</form>
{showElement ? (
<div className="submitmsg">
{message && (
<span> Messaged received. I'll respond to your query ASAP! </span>
)}
</div>
) : (
<div> </div>
)}{" "}
</div>
{errors.name && (
<div>
<span>{errors.name.message}</span>
</div>
)}
{errors.message && (
<div>
<span>{errors.message.message}</span>
</div>
)}
{errors.email && (
<div>
<span>{errors.email.message}</span>
</div>
)}
{errors.mobile && (
<div>
<span>{errors.mobile.message}</span>
</div>
)}
</div>
);
}
You have to first initialize handleSubmit as below.
const {handleSubmit} = useForm();
Then in the form, onSubmit should be as below.
<form onSubmit={handleSubmit(onSubmit)}>
"onSubmit" is the method that is used to write the code in submitting form.
Regards.
In your code, it should be as below.
const onSubmit = (e) => {
e.preventDefault();
emailjs.sendForm('', '', e.target, '')
.then((result) => {
console.log(result.text);
}, (error) => {
console.log(error.text);
});
e.target.reset();
setMessage(true);
setShowElement(true);
setTimeout(function () {
setShowElement(false);
}, 4000);
};
I am getting state values while clicking submit button but I am unable to do the validation for my login form and how to display the error messages below the input field when I enter my input wrong or empty. please give me a solution to this.Thanks in advance.
const Login = () => {
const [state, setState] = useState({
email: "",
password: ""
});
const handleChange = (e) => {
const {id, value} = e.target
setState(prevState => ({
...prevState,
[id]: value
}))
}
const handleSubmitClick = (e) => {
e.preventDefault();
console.log("Authenticated",state);
}
return(
<>
<div className="container">
<div className="title">
<form onSubmit={handleSubmitClick}>
<div className="form-group">
<input
type="email"
className="email"
placeholder="Email"
value={state.email}
onChange={handleChange}/>
</div>
<div className="form-group">
<input
type="password"
className="password"
placeholder="Password"
value={state.password}
onChange={handleChange}/>
</div>
<button type="submit" className="button">Enter</button>
</form>
</div>
</div>
</>
)
}
export default Login;
If you want to perform client-side validation, you can create hook like this:
const useEmailValidation = (email) => {
const isEmailValid = /#/.test(email); // use any validator you want
return isEmailValid;
};
And then you can use this hook in your form component:
...
const isEmailValid = useEmailValidation(state.email);
const isPasswordValid = usePasswordValidation(state.password);
const isFormValid = isEmailValid && isPasswordValid;
return (
...
<input
className={classNames({ 'invalid': !isEmailValid })}
type="email"
value={state.email}
onChange={handleChange}
/>
{!isEmailValid && 'Some error message'}
<button type="submit" disabled={!isFormValid} className="button">Enter</button>
...
);
...
Your validator hook can return validation message instead of boolean, like:
const useEmailValidation = (email) => {
if (!email || email.length === 0) {
return 'Email cannot be empty';
}
const isEmailValid = /#/.test(email); // use any validator you want
if (!isEmailValid) {
return 'Invalid email provided';
}
return null;
};
Also it is a good practice to show validation message only after field was focused before and after user tried to submit the form.
Formik is a great plugin that will help you perform form validation. The examples are also quite clear.
Or you could do something like this:
const Login = () => {
const [error, setError] = useState(null);
const [state, setState] = useState({
email: '',
password: '',
});
const validateEmail = (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,}))$/;
return re.test(String(email).toLowerCase());
};
const handleChange = (e) => {
const { id, value } = e.target;
setState((prevState) => ({
...prevState,
[id]: value,
}));
};
const handleSubmitClick = (e) => {
e.preventDefault();
if (!validateEmail(state.email)) {
setError('Invalid Email');
}
if (state.password.length < 8) {
setError('Password must be at least 8 chars long');
}
if (!error) {
// No errors.
}
};
return (
<>
<div className='container'>
<div className='title'>
{error && <div style={{ color: 'red' }}>{error}</div>}
<form onSubmit={handleSubmitClick}>
<div className='form-group'>
<input
type='email'
className='email'
placeholder='Email'
value={state.email}
onChange={handleChange}
/>
</div>
<div className='form-group'>
<input
type='password'
className='password'
placeholder='Password'
value={state.password}
onChange={handleChange}
/>
</div>
<button type='submit' className='button'>
Enter
</button>
</form>
</div>
</div>
</>
);
};
export default Login;
For an empty validation you can check it preventing the submit if the field is empty, like
const handleSubmitClick = (e) => {
e.preventDefault();
if(email.trim() === '' || password.trim() === ''){
//Add a h1 or section with the error message
}else{
console.log("Authenticated",state);
}
}
As long as the email field type is equal to email, which is your case, the browser should give an alert if the string is not an email. ("user#example.com")
I've got a form component, where you can add multiple inputs on click. I am using useState to store data from all of the inputs, but the problem is I am receiving a message that my inputs are uncontrolled
State data structure
const [formData, setFormData] = useState({
title: '',
titleNum: '',
meta: [
{
content: '',
contentNum: '',
details: '',
tags: ''
}
]
});
The idea is to dynamically add multiple objects via form to meta array
onChange events added to inputs
const { title, titleNum, meta } = formData;
const handleMainChange = e => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubChange = (e, index) => {
const { name, value } = e.target;
let metaContent = [...meta];
metaContent[index] = { ...metaContent[index], [name]: value };
setFormData({ ...formData, meta: metaContent });
};
Adding and removing inputs
const handleAddInput = () => {
setFormData(prevState => ({
meta: [
...prevState.meta,
{ content: '', contentNum: '', details: '', tags: '' }
]
}));
};
const handleRemove = index => {
let metaContent = [...meta];
metaContent.splice(index, 1);
setFormData({ meta: metaContent });
};
Submit and looping through all META
const onFormSubmit = e => {
e.preventDefault();
createAccount(formData, history);
};
const renderList = meta.map((item, index) => {
console.log(item);
return (
<AddMeta
key={index}
index={index}
meta={item}
handleChange={e => handleSubChange(e, index)}
handleRemove={() => handleRemove(index)}
/>
);
});
AddMeta Component
const AddMeta = ({ index, meta, handleChange, handleRemove }) => {
return (
<Fragment>
<div className='row valign-wrapper'>
<div className='col s3'>
<h5 className='indigo-text text-lighten-1'>Add Meta - {index + 1}</h5>
</div>
<div className='col s9'>
<button onClick={() => handleRemove(index)} className='btn red'>
Remove Meta Content
</button>
</div>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Text'
name='content'
value={meta.content || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Number'
name='contentNum'
value={meta.contentNum || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<textarea
className='materialize-textarea'
type='text'
placeholder='Details'
name='details'
value={meta.details || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Tags'
name='tags'
value={meta.tags || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
</Fragment>
);
};
I appreciate any attempt to solve this problem.
Thanks!
Try removing the || '' on your inputs value. Their value should always be tied to your state (which should be '' when you add a new meta).