Getting an Uncaught (in promise) TypeError: Cannot read properties of undefined when using Yup.string.test() for a validation. Using Formik and Yup - formik

I'm trying to implement a third party validation inside a useFormik && Yup validation, but I'm getting a Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'isValidIBAN')the moment I'm trying to start typing something on the input. It breaks on the Yup.test() function, I don't know what I 'm doing wrong.
This is my existing code.
import { useFormik } from 'formik'
import * as Yup from 'yup'
import ibantools, { electronicFormatIBAN } from 'ibantools'
function BankDetails({}: BankDetailsProps) {
// ommitted code
const formik = useFormik({
initialValues: {
bankAccountNumber: '',
bicNumber: '',
policyNumber: '',
},
validationSchema: Yup.object({
bankAccountNumber: Yup.string()
.transform(function (value) {
if (!this.isType(value)) return value
return electronicFormatIBAN(value). // Converts the user input into a valid IBAN
})
.test({
name: 'is-valid-iban',
message: t('Forms.BankDetails.Errors.InvalidIBAN'),
exclusive: true,
// #ts-ignore
test: (value) => ibantools.isValidIBAN(value), // TESTS that user input is a valid IBAN [ERROR]: Typerror here. Uncaught in Promise. Cannot read promerty of undefined reading
// console.log(value, 'inside iban')
})
})
})
}
return {
<form className="flex flex-col pt-4">
<div>
<Label htmlFor="bankAccountNumber" className="mt-2">
{t('Forms.BankDetails.IBANLabel')}
</Label>
<Input
kind="text"
error={formik.errors.bankAccountNumber}
name="bankAccountNumber"
value={formik.values.bankAccountNumber}
touched={formik.touched.bankAccountNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</div>
</form>
}
What I would like is to
get user input
convert it into a valid iban using electronicFormatIBAN(value)
test that it is a valid iban with isValidIBAN() inside the Yup.test()
return that string

I just solved the issue by removing the ibantools from this call
.test({
name: 'is-valid-iban',
message: t('Forms.BankDetails.Errors.InvalidIBAN'),
exclusive: true,
// #ts-ignore
test: (value) => ibantools.isValidIBAN(value), // [NOTE] remove ibantools from here
// console.log(value, 'inside iban')
})
Now just doing it liek this and it works
.test({
name: 'is-valid-iban',
message: t('Forms.BankDetails.Errors.InvalidIBAN'),
exclusive: true,
// #ts-ignore
test: (value) => isValidIBAN(value),
})
However how come ibantools is not available inside that .test() context but isValidIBAN is ??

Related

Formik displaying wrong error message when Yup async test is used

Versions:
formik: 2.2.6
yup: 0.32.8
I've been having this issue with formik today so I set about creating a minimally reproducible example.
In the example you'll see that we have different validators attached to the data field including: required, length(6) and an async test.
When the correct conditions are met for an error message to show, you'll notice that the wrong error message is shown until the async test resolves. You'll also notice that between the wrong message showing, and the async test resolving, formik.isValidating is false.
What are we doing wrong here and how can we prevent this incorrect error message from showing up?
Additionally, how can we get formik.isValidating to return true when the async test is yet to resolve? If we could do that we could indicate to the user that validation is still pending.
Minimal Reproducible Code: https://codesandbox.io/s/black-shape-z5e54?file=/src/App.js
import React, { useEffect, useRef } from "react";
import "./styles.css";
import { useFormik } from "formik";
import * as yup from "yup";
const asyncTest = (value, context) => {
return new Promise((res) => {
setTimeout(() => {
res(context.createError({ message: "This ain't right" }));
}, 3000);
});
};
const validationSchema = yup.object().shape({
data: yup
.string()
.required("Data is required")
.length(6)
.test("async-val", "", asyncTest)
});
export default function App() {
const formik = useFormik({
validationSchema,
initialValues: {
data: ""
}
});
// A helper to simulate a field that auto blurs
// when completing. In the real code, handleBlur is called
// when the pin input reaches its sixth character.
const inputRef = useRef(null);
useEffect(() => {
if (formik.values.data.length >= 6) {
inputRef.current.blur();
}
}, [formik.values.data]);
return (
<>
<input
type="text"
name="data"
ref={inputRef}
value={formik.values.data}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.errors.data && formik.touched.data && (
<div>{formik.errors.data}</div>
)}
</>
);
}

redux form - validate input matches specific length

I'm working with Redux Form and creating validation that will check if the field matches exactly X number of characters. My approach is to determine the length of the input and validate against a specified length. I am able to successfully console log each character input, but receive an error when trying to log the length of the input.
How can I determine the length of the input within the validator.js file
Error message: "TypeError: Cannot read property 'length' of undefined"
//component.js (Redux Form)
import React from 'react';
import {reduxForm, Field, SubmissionError, focus} from 'redux-form';
import Input from './input';
import {exactSix} from '../validators';
export class Contact extends React.Component {
// onSubmit(values) {}
render() {
return (
<form
onSubmit={this.props.handleSubmit(values =>
this.onSubmit(values)
)}>
<Field
name="content"
type="text"
component={Input}
label="Match"
validate={[exactSix]}
/>
<button
type="submit"
disabled={this.props.pristine || this.props.submitting}>
Send message
</button>
</form>
);
}
}
export default reduxForm({
form: 'contact',
onSubmitFail: (errors, dispatch) =>
dispatch(focus('contact', Object.keys(errors)[0]))
})(Contact);
//validator.js
export const exactSix = value => {
console.log(value);
console.log(values.length); //this line is highlighted within the error message
if(value.length === 6){
return undefined;
} else {
return 'must be exactly six';
}
}
You need to check if value is not undefined:
export const exactSix = value => {
return value && value.length !== length
? `must be exactly six`
: undefined;
};
Codesandbox demo

How can I throw a SubmissionError from a non-Submit function?

I'm using redux-form. I have an onSubmit and a checkStatus method which both make an Ajax call to the API back-end. If the result of the Ajax call is not 200, I want to throw a SubmissionError.
The SubmissionError works fine if I throw it inside of onSubmit (it correctly shows me an error message on the form). But if I throw it inside of checkStatus, then I get an error complaining about an handled exception:
Uncaught (in promise) SubmissionError {errors: {…}, message: "Submit
Validation Failed", name: "SubmissionError", stack: "SubmissionError:
Submit Validation Failed at ...) at <anonymous>"}
I did see this comment that makes is seem like you are only supposed to throw SubmissionError from within onSubmit():
Here is my code:
import * as React from "react";
import { reduxForm, touch, Form, SubmissionError } from 'redux-form';
import ReduxFormField from './components/ReduxFormField'
export interface FormProps {
handleSubmit: any;
error: any;
}
export class MyForm extends React.Component<FormProps, any> {
private _inputVal: any;
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.checkStatus = this.checkStatus.bind(this);
}
checkStatus() {
return fetch("/checkStatus", {
method: 'GET',
}).then(response => {
if (response.status === 200) {
// checkStatus was successful!
} else {
throw new SubmissionError({
_error: "There was an error."
});
}
});
}
onSubmit(event) {
return fetch("/submit", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({'key1': 'val1'})
}).then(response => {
if (response.status === 200) {
// submit was successful!
} else {
throw new SubmissionError({
_error: "There was an error."
});
}
});
}
render() {
return (
<form onSubmit={this.props.handleSubmit(this.onSubmit)}>
<div>
<fieldset>
<ReduxFormField
type="text" fieldName="myInputValue"
ref={(input) => {
this._inputVal = input;
}}/>
</fieldset>
<input type="submit" value="Submit"/>
<a onClick={() => {
this.checkStatus()
}
}>Check Status
</a>
</div>
</form>
)
}
}
export default reduxForm({
form: 'myForm'
})(MyForm);
I suppose this action creator should help
https://redux-form.com/7.2.0/docs/api/actioncreators.md/#-code-setsubmitfailed-form-string-fields-string-code-
setSubmitFailed(form:String, ...fields:String) #
Flips submitFailed flag true, removes submitSucceeded and submitting, marks all the fields passed in as touched, and if at least one field was passed flips anyTouched to true.
Another option if to create a formField ( I don't mean a visible field, but just inside redux-form) called 'checkStatus' being true by default. When your check fails, simply dispatch action called change onto this field and supply the reduxForm with a validator which fails if field if false

Angular 2 (Beta) Server side validation messages

I am looking for an elegant way to display validation messages from a server side API without having to create custom validators or hard coding all possible messages in the UI.
I need to add error messages to specific fields as well as to the entire form.
This must work in Angular 2.0.0-beta.3
There are two kinds of server validations:
The global ones (for the whole form or corresponding to an error during the form submission)
The ones related to fields
For the one, simply extract the message from the response payload and put it into a property of your component to display it into the associated template:
#Component({
(...)
template: `
<form (submit)="onSubmit()">
(...)
<div *ngIf="errorMessage">{{errorMessage}}</div>
<button type="submit">Submit</button>
</form>
`
})
export class MyComponent {
(...)
onSubmit() {
this.http.post('http://...', data)
.map(res => res.json())
.subscribe(
(data) => {
// Success callback
},
(errorData) => {
// Error callback
var error = errorData.json();
this.error = `${error.reasonPhrase} (${error.code})`;
}
);
}
}
I assume that the response payload for error is a JSON one and corresponds to the following:
{
"code": 422,
"description": "Some description",
"reasonPhrase": "Unprocessable Entity"
}
For the second one, you can set received error message within controls associated with form inputs, as described below:
#Component({
(...)
template: `
<form [ngFormModel]="myForm" (submit)="onSubmit()">
(...)
Name: <input [ngFormControl]="myForm.controls.name"/>
<span *ngIf="myForm.controls.name.errors?.remote"></span>
(...)
<button type="submit">Submit</button>
</form>
`
})
export class MyComponent {
(...)
constructor(builder:FormBuilder) {
this.myForm = this.companyForm = builder.group({
name: ['', Validators.required ]
});
}
onSubmit() {
this.http.post('http://...', data)
.map(res => res.json())
.subscribe(
(data) => {
// Success callback
},
(errorData) => {
// Error callback
var error = errorData.json();
var messages = error.messages;
messages.forEach((message) => {
this.companyForm.controls[message.property].setErrors({
remote: message.message });
});
}
);
}
}
I assume that the response payload for error is a JSON one and corresponds to the following:
{
messages: [
{
"property": "name",
"message": "The value can't be empty"
]
}
For more details you can have a look at this project:
https://github.com/restlet/restlet-sample-angular2-forms/blob/master/src/app/components/company.details.ts
https://github.com/restlet/restlet-sample-angular2-forms/blob/master/src/app/components/form.field.ts
I present you the definitive displayErrors function (Handles server side validations following the JSONAPI Standard):
You will need Underscore.js
displayErrors(error: ErrorResponse) {
let controls = this.supportRequestForm.controls;
let grouped = _.groupBy(error['errors'], function(e) {
return e['source']['pointer'];
});
_.each(grouped, function(value, key, object) {
let attribute = key.split('/').pop();
let details = _.map(value, function(item) { return item['detail']; });
controls[attribute].setErrors({ remote: details.join(', ') });
});
}

Make server validation using redux-form and Fetch API

How to make server-side validation using redux-form and Fetch API?
There are "Submit Validation" demo provided in the docs which says that recommended way to do server side validation is to return a promise from the onSubmit function. But where should I place that promise?
As I understood onSubmit function should be my action.
<form onSubmit={this.props.addWidget}>...
Where this.props.addWidget is actually my action, provided below.
import fetch from 'isomorphic-fetch';
...
function fetchAddWidget(widget, workspace) {
return dispatch => {
dispatch(requestAddWidget(widget, workspace));
return fetch.post(`/service/workspace/${workspace}/widget`, widget)
.then(parseJSON)
.then(json => {
dispatch(successAddWidget(json, workspace));
DataManager.handleSubscribes(json);
})
.catch(error => popupErrorMessages(error));
}
}
export function addWidget(data, workspace) {
return (dispatch, getState) => {
return dispatch(fetchAddWidget(data, workspace));
}
}
As you see I use fetch API. I expected that fetch will return promise and redux-form will catch it but that doesn't work. How to make it work with promise from example?
Also from the demo I can not understand what should be provided in this.props.handleSubmit function. Demo does not explain this part, as for me.
Here's my take on using fetch based on the example at http://erikras.github.io/redux-form/#/examples/submit-validation.
...but where should I place that promise?
...what should be provided in this.props.handleSubmit?
The detail is in the comments below; sorry that the code blocks require a bit of scrolling to read :/
components/submitValidation.js
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { myHandleSubmit, show as showResults } from '../redux/modules/submission';
class SubmitValidationForm extends Component {
// the following three props are all provided by the reduxForm() wrapper / decorator
static propTypes = {
// the field names we passed in the wrapper;
// each field is now an object with properties:
// value, error, touched, dirty, etc
// and methods onFocus, onBlur, etc
fields: PropTypes.object.isRequired,
// handleSubmit is _how_ to handle submission:
// eg, preventDefault, validate, etc
// not _what_ constitutes or follows success or fail.. that's up to us
// I must pass a submit function to this form, but I can either:
// a) import or define a function in this component (see above), then:
// `<form onSubmit={ this.props.handleSubmit(myHandleSubmit) }>`, or
// b) pass that function to this component as
// `<SubmitValidationForm onSubmit={ myHandleSubmit } etc />`, then
// `<form onSubmit={this.props.handleSubmit}>`
handleSubmit: PropTypes.func.isRequired,
// redux-form listens for `reject({_error: 'my error'})`, we receive `this.props.error`
error: PropTypes.string
};
render() {
const { fields: { username, password }, error, handleSubmit } = this.props;
return (
<form onSubmit={ handleSubmit(myHandleSubmit) }>
<input type="text" {...username} />
{
// this can be read as "if touched and error, then render div"
username.touched && username.error && <div className="form-error">{ username.error }</div>
}
<input type="password" {...password} />
{ password.touched && password.error && <div className="form-error">{ password.error }</div> }
{
// this is the generic error, passed through as { _error: 'something wrong' }
error && <div className="text-center text-danger">{ error }</div>
}
// not sure why in the example #erikras uses
// `onClick={ handleSubmit }` here.. I suspect a typo.
// because I'm using `type="submit"` this button will trigger onSubmit
<button type="submit">Log In</button>
</form>
);
}
}
// this is the Higher Order Component I've been referring to
// as the wrapper, and it may also be written as a #decorator
export default reduxForm({
form: 'submitValidation',
fields: ['username', 'password'] // we send only field names here
})(SubmitValidationForm);
../redux/modules/submission.js
// (assume appropriate imports)
function postToApi(values) {
return fetch( API_ENDPOINT, {
credentials: 'include',
mode: 'cors',
method: 'post',
body: JSON.stringify({values}),
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': CSRF_TOKEN
}
}).then( response => Promise.all([ response, response.json()] ));
}
export const myHandleSubmit = (values, dispatch) => {
dispatch(startLoading());
return new Promise((resolve, reject) => {
// postToApi is a wrapper around fetch
postToApi(values)
.then(([ response, json ]) => {
dispatch(stopLoading());
// your statuses may be different, I only care about 202 and 400
if (response.status === 202) {
dispatch(showResults(values));
resolve();
}
else if (response.status === 400) {
// here I expect that the server will return the shape:
// {
// username: 'User does not exist',
// password: 'Wrong password',
// _error: 'Login failed!'
// }
reject(json.errors);
}
else {
// we're not sure what happened, but handle it:
// our Error will get passed straight to `.catch()`
throw(new Error('Something went horribly wrong!'));
}
})
.catch( error => {
// Otherwise unhandled server error
dispatch(stopLoading());
reject({ _error: error });
});
});
};
Please chime in with comments if I've missed something / misinterpreted, etc, and I'll amend :)
It turned out that there are undocumented property returnRejectedSubmitPromise which must be set to true.

Resources