formik onChange working not asynchronously - reactjs

name: Yup.string()
.required(),
});
const {
errors,
touched,
getFieldProps,
handleChange,
} = formik;
function onChange(e) {
if (Object.keys(errors).length === 0) {
**(HERE I HAVE errors.length 0, despite this my input is empty and is required, so I shuld have errors here. I have error here, but with delay, )**
dispatch(setInvitationConfigParam({ name: e.target.name, value: e.target.value }));
}
handleChange(e);
}
<InputWrapper>
<TextInput
customClass="input"
error={false}
textFieldProps={{
label: t('configurationForm.settings.nameConfiguration'),
error: hasErrors('name', errors, touched),
helperText: getErrors('name', errors, touched),
...getFieldProps('name'),
onChange,
}}
tooltipTitle={t('configurationForm.settings.nameConfigurationDescription')}
/>
What I want and why I have problem.
I want to sending dispach function every time when user change his input name, but if the input name is empty I didn't want sendig dispatch.
The problem is that: object errors show errors with a delay.
how can I have errors with no dealy?

Related

react-hook-form + yup validation field value

I`ve got this yup schema:
`const ModalEditCard = () => {
const validationSchema = Yup.object().shape({
title: Yup.string()
.required("Поле обязательно для заполнения")
.max(50, "Заголовок не более 50 символов")
});`
React-hook-form options:
const {
register,
handleSubmit,
formState: { errors, isValid }
} = useForm<UserSubmitForm>({
resolver: yupResolver(validationSchema),
mode: "onSubmit",
reValidateMode: "onChange",
});
and this textarea:
` <textarea
{...register(name)}
rows={1}
className="modal__textArea"
placeholder={placeholder}
/>`
I whant to show how mach symbols user has to write. Example I have max 50 symbols for textarea. If I write hello, under field i want to see "45". And it`s should be just message. Not validate
I had custom hook for count symbols. But i think, Yup could help me

Formik does not get date value when it returns from function

I created an array of objects that has form field definitions in order to quickly generate dynamic user forms, in react.
export const formFieldList = [
{
fieldType: 'input',
inputType: 'date',
name: 'LastLoginDate',
id: 'LastLoginDate',
label: 'Last Login Date',
value: moment(new Date()).format('YYYY-MM-DD'),
classWrapper: 'col-md-3',
}]
The problem occurred when I changed this constant array to a function. I need to get some data from async functions so I changed this array to a async func as well. All other form fields (text, select, checkbox) still work well but date fields, formik doest get date value after then.
export const formFieldList = async () => ([
{
fieldType: 'input',
inputType: 'date',
name: 'LastLoginDate',
id: 'LastLoginDate',
label: 'Last Login Date',
value: moment(new Date()).format('YYYY-MM-DD'),
classWrapper: 'col-md-3',
},
{
fieldType: 'select',
name: 'CurrencyId',
id: 'CurrencyId',
label: 'Currencies',
classWrapper: 'col-md-3',
options: await getCurrencies()
}]
If I write date value as hard-coded, it doesnt change anything, I mean function returns everything well, the problem is formik doesnt get that value as a prop when it returns from a function. My input component that get that values:
<input
{...formik.getFieldProps(Field.name)}
type={Field.inputType}
placeholder={Field.placeholder}
name={Field.name}
id={Field.id || Field.name}
/>
In the DOM, input element is shown as <input value name='xx' ... />.
My FormGenerator component that takes formFieldList and returns it to form:
const FormGenerator:FC<Props> = ({formFieldList, formButton, onSubmitValues}) => {
...
let initialValues = {}
for (const Field of formFieldList) {
if (Field.name !== 'newLine'){
initialValues = {...initialValues, [Field.name]: Field.value}
}
}
const schema = formFieldList.reduce(getValidationSchema, {})
const validationSchema = Yup.object().shape(schema)
const formik = useFormik({
initialValues,
enableReinitialize: true,
validationSchema,
onSubmit: async (values, {setStatus, setSubmitting}) => {
setLoading(true)
try {
onSubmitValues(values)
setLoading(false)
} catch (error) {
console.error(error)
setStatus('Some errors occurred')
setSubmitting(false)
setLoading(false)
}
},
})
return (
...
<form
onSubmit={formik.handleSubmit}
noValidate
>
{ formFieldList?.map((Field, index) => {
const touchedField = Field.name as keyof typeof formik.touched;
const errorsField = Field.name as keyof typeof formik.errors;
switch (Field.fieldType) {
case 'input':
return <InputField Field={Field} formik={formik} touchedField={touchedField} errorsField={errorsField} key={Field.name}/>
case 'select':
return <SelectField Field={Field} formik={formik} key={Field.name}/>
case 'newLine':
return <div className={Field.classWrapper} key={index}></div>
}
})}
...
</form>
</>
)
}
Updating:
After adding 'enableReinitialize' (with help of #Sheraff), form show dates and works well, but console give me an error as 'A component is changing an uncontrolled input to be controlled...'. Then I added value property to input element to make it controlled. But now date picker doesnt update ui when I change date manuelly (it returns correct date as its value). If I enter value 'undefined' in formFieldList then it works again, but this time I coundt initialize my date dynamically.
How can I fix it?

How to add validation for status code 400 in react

I am trying to edit a software program that requires a validation for an error on "Uncaught (in promise) Error: Request failed with status code 400"
Reason for this is that the user is trying to create a same unique data on the table. that is why it's getting a status code 400.
So now, I want to have a error handler for the status code 400 and will return a String in a text field stating "Duplicate Entry"
Here are my current validations.
import * as Yup from "yup";
const InvoiceValidationSchema = Yup.object().shape({
customer: Yup.object()
.required("Customer is required."),
invoiceDate: Yup.string()
.required("Invoice Date is required"),
dueDate: Yup.string()
.required("Due Date is required."),
invoiceNumber: Yup.string()
.required("Invoice Number is required"),
invoiceLines: Yup.array()
.of(
Yup.object().shape({
product: Yup.object().required("Required"),
quantity: Yup.number().required("Required.").min(1, "Quantity cannot be zero."),
// price: Yup.number().required("Required.").min(1, "Quantity cannot be zero.").typeError("")
})
).required("Required")
});
export default InvoiceValidationSchema;
I hope anyone can help me. Thanks
Here is the formik
<Formik
validationSchema={InvoiceValidationSchema}
initialValues={invoiceCrud}
onSubmit={(values, action) => {
const invoiceLines = values.invoiceLines.map(invoiceLine => {
return {
...invoiceLine,
product: invoiceLine.product,
price: invoiceLine.price,
quantity: invoiceLine.quantity,
totalAmount: invoiceLine.quantity * invoiceLine.price,
averagePurchasePrice: invoiceLine.product.averagePurchasePrice
}
})
const subTotal = invoiceLines.reduce((sum, line) => {
return sum + line.totalAmount;
},0)
const totalExpense = invoiceLines.reduce((sum, line) => {
return sum + (line.product.averagePurchasePrice * line.quantity);
},0)
const totalAdjustment = 0;
const netTotal = subTotal - totalAdjustment;
const totalDueAmount = subTotal - values.subTotal + values.totalDueAmount;
const invoice ={
...values,
invoiceLines,
subTotal,
totalAdjustment,
netTotal,
totalExpense,
totalDueAmount
}
if(values.invoiceLines[0].price > 0 && values.invoiceLines[0].quantity > 0){
submit(invoice);
setTimeout(() => {
action.setSubmitting(false);
}, 1000);
}else{
setIsZero(true)
action.setSubmitting(false)
}
}}
render={
({ values, errors, touched, setFieldValue, isSubmitting }) => (
<Form>
<div className="box-body" id="invoice-crud">
<div className="row">
<div className="col-lg-3 col-md-6 col-sm-12">
{errors.invoiceLines ? setIsProductSelected(false) : null}
{errors.customer ? setCustomerClass("btn btn-default dropdown-toggle transaction-form__account has-error")
: setCustomerClass("btn btn-default dropdown-toggle transaction-form__account")}
<DropdownWithSearch
property={
{
title: "Customer",
buttonClass: CustomerClass,
buttonLabel: values.customer ? values.customer.partnerName : "Select Customer",
newButtonLabel: "Customer",
showRemove: values.customer,
modalSelector: "#partner-form"
}
}
option={{
list: partnerDetails.list,
total: partnerDetails.total,
currentPage: partnerDetails.currentPage,
pageSize: partnerDetails.pageSize,
load: changePartnerPage,
display: (element) => element.partnerName,
onClick: (element) => {
setFieldValue("customer", element)
},
removeSelected: () => {
setFieldValue("customer", null)
},
search: (search) => {
dropdownSearch(search, "partner")
}
}}
/>{errors.customer ? <span className="errorMessage">Please select a customer</span> : null}
</div>
<FormRow validation={{errors: errors, touched: touched}} field={invoiceNumber}/>
</Form>
)}
</Formik>
Question seems to be related to Formik and handling backend errors in it, rather than anything else.
You can check how to set manually backend errors in formik in this github issue.
And in particular this sandbox, posted by user #t-nunes in github.
If your function which makes the request is written in the new async/await style, you'll probably handle your errors in a try / catch block, and for the old .then, .catch style, you'll be using the .catch callback as long as server returns error code.

Handling errors from api with Formik

I am catching errors from api and showing them in form, and that is working fine. But the problem is when I change one field in form all errors disappear. For form I am using Formik and for validation Yup.
const handleSubmit = (values, {setSubmitting, setFieldError, setStatus}) => {
someApiCall(values)
.then(
() => {
},
(error) => {
// example of setting error
setFieldError('email', 'email is already used');
})
.finally(() => {
setSubmitting(false)
});
};
I tried with adding third parametar false to setFieldError, but nothing changed.
Here's my working example: https://codesandbox.io/s/formik-example-dynamic-server-rendered-values-1uv4l
There's a callback validate available in Formik: https://jaredpalmer.com/formik/docs/api/formik#validate-values-values-formikerrors-values-promise-any using which you can probably try to do something like below.
I initiated emailsAlreadyInUse with empty array and then in your API call once error gets returned then add that user to the array and once user uses the same email again and tried to validate, although it will pass Yup validation but it will be caught in validate callback which I believe runs after Yup validation (though I could be wrong but in your case doesn't matter).
const emailsAlreadyInUse= [];
const handleSubmit = (values, {
setSubmitting,
setFieldError,
setStatus
}) => {
someApiCall(values)
.then(
() => {
// Do something
// possibly reset emailsAlreadyInUse if needed unless component is going to be unmounted.
},
(error) => {
// example of setting error
setFieldError('email', 'email is already used');
// Assuming error object you receive has data object that has email property
emailsAlreadyInUse.push(error.data.email);
})
.finally(() => {
setSubmitting(false)
});
};
<Formik
...
...
validate = {
values => {
let errors = {};
if (emailsAlreadyInUse.includes(values.email)) {
errors.email = 'email is already used';
}
return errors;
}
}
/>
I found simplier method to make API validation errors always visible than using validate method. You can set validateOnBlur and validateOnChange on your form to false. It will cause validation only on submit and errors returned from API will remain after changing field value.
Usage:
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validateOnBlur={false}
validateOnChange={false}
>
...form fields...
</Formik>

Error using validate.js on React Native - Unknown validator minimum

I am using validate.js from http://validatejs.org/ on react native log in screen.
import React, { Component } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity } from 'react-native';
// Validate.js validates your values as an object
import validate from 'validate.js'
const constraints = {
email: {
presence: {
message: "Cannot be blank."
},
email: {
message: 'Please enter a valid email address'
}
},
password: {
presence: {
message: "Cannot be blank."
},
length: {
minimum: 5,
message: 'Your password must be at least 5 characters'
}
}
}
const validator = (field, value) => {
// Creates an object based on the field name and field value
// e.g. let object = {email: 'email#example.com'}
let object = {}
object[field] = value
let constraint = constraints[field]
console.log(object, constraint)
// Validate against the constraint and hold the error messages
const result = validate(object, constraint)
console.log(object, constraint, result)
// If there is an error message, return it!
if (result) {
// Return only the field error message if there are multiple
return result[field][0]
}
return null
}
export default class Login extends Component {
state = {
email: '',
emailError: null,
password: '',
passwordError: null,
}
logIn = () => {
let { email, password } = this.state;
console.log( email, password)
let emailError = validator('email', email)
let passwordError = validator('password', password)
console.log( emailError, passwordError)
this.setState({
emailError: emailError,
passwordError: passwordError,
})
}
render() {
const {emailError, passwordError } = this.state
return (
<View>
<TextInput
onChangeText={(email) => this.setState({email})}
placeholder="Email Address"
keyboardType='email-address'
autoCapitalize='none'
/>
<Text> {emailError ? emailError : null }</Text>
<TextInput
onChangeText={(password) => this.setState({password})}
placeholder="Password"
secureTextEntry={true}
autoCapitalize='none'
type="password"
/>
<Text> {passwordError ? passwordError : null }</Text>
<TouchableOpacity onPress={this.logIn}>
<Text>LOG IN</Text>
</TouchableOpacity>
</View>
);
}
}
Running the code above logs the following and throws an error "Unknown validator message"
.. I ReactNativeJS: 't#t.cl', 'j'
.. I ReactNativeJS: { email: 't#t.cl' }, { presence: { message: 'Cannot be blank.' },
.. I ReactNativeJS: email: { message: 'Please enter a valid email address' } }
.. E ReactNativeJS: Unknown validator message
Your validate call is wrong. It should be:
const result = validate(object, { [field]: constraint })
Note that your object is:
{email: ...}
therefore the constraints passed to validation also need to be of the following form:
{email: emailConstraints }
What happens is that the validator looks into email and looks for validators (constraints) there but what it finds is only message and it prints
Unknown validator "message"
("message" being the name of the unknown constraint).
Try this instead, I have changed in below function and it works.
const validator = (field, value) => {
// Creates an object based on the field name and field value
// e.g. let object = {email: 'email#example.com'}
let object = new Object()
object[field] = value
let constraint = new Object()
constraint[field] = constraints[field]
console.log(object, constraint)
// Validate against the constraint and hold the error messages
const result = validate({}, constraint)//
if (value != '' && value != null) {//if null value it will return with the presence validation
result = validate(object, constraint)
}
// If there is an error message, return it!
if (result) {
// Return only the field error message if there are multiple
return result[field][0]
}
return null
}
I managed to make it work by making it like this:
let emailConstraints;
<TextInput
onChangeText={(emailConstraints) => this.setState({email: emailConstraints })}
placeholder="Email Address"
keyboardType='email-address'
autoCapitalize='none'
/>
<Text> {emailError ? emailError : null }</Text>
I had this specific problem but I could tell what the issue was. My error was Unknown validator checked. The problem is indicated by the word checked in that error message. It means that the attribute checked(in my case) and minimum(in your case) is used in a wrong place in the json of constraints. OR, it is in the right json block but one or many of the other attributes in that block should not be there and the validate.js parser reports the error using the first attribute in that block regardless of it being in the correct one. I had this
policy: {
presence: true,
checked: true}
for checkbox validation, but the library does not allow unknown attributchecked in that block. In your case, I think the message attribute should not be in the length block but validate.js reports the error using the first attribute minimum in that block which is otherwise in the right place. So you should possibly consider removing message attribute and things will be fine.

Resources