How do I validate a checkout form in React? - reactjs

I am trying to implement a checkout form in React. The form has 4 fields in all: Name, CC Number, CC expiration and CVV. I am using a library that validates each field on unfocus. The validation is triggered by the validationCallback method which takes 3 arguments: field, status, and message. I'd like to key off of the status for each input and only allow submit once each status === true. Here is my code.
constructor(props) {
super(props);
this.state = {
nameOnCard: '',
errorMessage: '',
showLoaderForPayment: '',
collectJs: null,
token: null,
isPaymentRequestCalled: false,
showErrorModal: false,
paymentErrorText: '',
disabled: true,
};
}
I have a disabled property in my state which I'm initially setting to true.
validationCallback: (field, status, message) => {
if (status) {
this.setState({ errorMessage: '' });
} else {
let fieldName = '';
switch (field) {
case 'ccnumber':
fieldName = 'Credit Card';
break;
case 'ccexp':
fieldName = 'Expire Date';
break;
case 'cvv':
fieldName = 'Security Code';
break;
default:
fieldName = 'A';
}
if (message === 'Field is empty') {
this.setState({ errorMessage: `${fieldName} ${message}` });
} else {
this.setState({ errorMessage: `${message}` });
}
}
},
In the above method, I'd like to set disabled to false if each of the field's status===true... Below is the button which I'm setting to be the value of this.state.disabled.
<button
className="continueBtn disabled"
disabled={this.state.disabled}
onClick={this.handleCardSubmit}
>
<span className="fa fa-lock" />
Pay $
{selectedPayment.amount}
</button>
I hope this is enough of the code to help with the issue. I can provide more of the file if need be.

From what i understand, you want to set the button to NOT DISABLED if all the fields are filled properly, i.e. all status are true.
What you can do is maintain a boolean array for each field and update the status in that array, i.e. initialize an array of length = no. of fields (in your case 3) and set all values as false. False depicts that the field hasn't been validated.
this.state = {
statusArray = [false, false, false] // For as many fields
}
Then in validationCallback, set the index as true or false for that field i.e. if the 2nd field status is returned true by your validation library, set statusArray as [false, true, false].
The form will only be validated if all 3 of the values become true. So you can iterate over the array and check if array has all 3 values as true. or you can use the logical AND operator which returns true only if all values are true(the approach which i use below).
For the button,
<button disabled={this.checkDisable()}>
checkDisable = () => {
let temp = this.state.statusArray;
let answer = true;
for(int i=0;i<temp.length;i++)
answer = answer && temp[i];
return answer; // Only returns true if all 3 values are true
}
I hope you get it now.

You need to check 2 things, has the form been touched and are there any errors. I don't know what library you are using but most likely it has a property touched in it, if not add an onFocus to each input field and a touched property in your state. You don't really need a disabled property in your state since its a computed value. Just check on every render if the form has been touched and if there are any errors.
state = {
...,
touched: false,
...
}
handleFocus = () => this.setState({touched: true})
render(){
const disabled = !!(this.state.touched && this.state.errorCode)
return(
...
<input onFocus={this.handleFocus} ... />
...
<button disabled={disabled}
)
}
EDIT:
state = {
...
validInputs: []
}
validationCallback: (field, status, message) => {
if (status) {
this.setState((state) => ({ errorMessage: '', validInputs: [... new Set([...state.validInputs, field])] }));
} else {
...
render(){
const disabled = this.state.length < inputs.length // the number of the input fields
return(
...
<button disabled={disabled} >
...
)

Related

react-hook-form Controller onBlur callback not triggering validation

Want to trim an input field when user onBlurs.
<Controller
...
onBlur={([e]) => {
const { value } = e.target;
const trimmedValue = value.trim();
console.log('trim here: ', value, value.length, trimmedValue.length);
if (trimmedValue === '') {
console.log('error!!!');
return trimmedValue;
}
return trimmedValue;
}} />
Rule:
rules={{
pattern: {
value: new RegExp(firstName.validationString, 'i'),
message: 'First name must be 2 - 100 characters with no numbers.',
},
required: firstName.mandatory && 'Must fill in first name',
}}
The function is triggered and reaching the if-statement. But is not triggering an error even though I have a rule set as required.
use this in your register or controller
validate: (value) => { return !!value.trim()}
Take a look here: https://github.com/react-hook-form/react-hook-form/issues/1650

FormArray does not print on my Reactive Form

I'm starting at Angular and I'm having a really hard time setting a FormArray inside my form. In summary what I did was:
Create 2 forms: the main (valForm) and another one created dynamically (holidaysForm).
Copy the values of the second form to an Array.
Load the values of the Array into a FormArray.
Follow the codes:
.ts
let group = {}
this.holidaysForm = new FormGroup(group);
this.valForm = fb.group({
dentistId: [null, Validators.required],
initialHour: [null, Validators.required],
endHour: [null, Validators.required],
numberOfHolidays: [null],
appointmentsInterval: [null, Validators.required],
year: [null, Validators.required],
workDays: this.buildDays(),
holidays: this.buildHolidays()
});
buildDays() {
const values = this.workDays.map(v => new FormControl(false));
return this.fb.array(values);
}
buildHolidays() {
if (typeof this.valForm !== 'undefined') {
let teste = Object.assign({}, this.holidaysForm.value);
this.holidays = new Array();
Object.values(teste).forEach((v) => {
this.holidays.push(v);
})
console.log(this.holidays);
const values = this.holidays.map((v)=> new FormControl(v));
return this.fb.array(values);
}
}
dynamicForm(val) {
if (val > 365) {
val = 365;
this.valForm.patchValue({
numberOfHolidays: 365
})
}
const val_plus_one = parseInt(val) + 1
let i: number = val_plus_one;
if (i < this.oldNumber) {
do {
this.holidaysForm.get('holiday' + i.toString()).setValue(null);
this.holidaysForm.removeControl('holiday' + i.toString());
i++
} while (i <= this.oldNumber)
}
const numbers = Array(val).fill(val, 0, 365).map((_, idx) => idx + 1)
numbers.forEach((num) => {
const fc = new FormControl('', FormValidations.Calendar(this.valForm.get('year').value));
this.holidaysForm.addControl('holiday' + num.toString(), fc)
})
this.numberOfHolidays = numbers;
this.oldNumber = val;
}
.html
<div class="col-md-4 mda-form-group">
<input class="mda-form-control" type="number" min="0" max="365"
formControlName="numberOfHolidays"
(change)="dynamicForm(valForm.get('numberOfHolidays').value)" />
<label>Total de Feriados</label>
</div>
<div [formGroup]="holidaysForm">
<div class="mda-form-group" *ngFor="let num of numberOfHolidays">
<input class="mda-form-control" type="date" formControlName="holiday{{num}}" />
<label>Feriado {{num}}</label>
<app-error-msg [control]="holidaysForm.get('holiday'+num)" label="Feriado">
</app-error-msg>
</div>
</div>
In theory the code works well and without errors, the problem is that the result is always this:
Main Form
Valores:
{
"dentistId": null,
"initialHour": null,
"endHour": null,
"numberOfHolidays": 3,
"appointmentsInterval": null,
"year": null,
"workDays": [
false,
false,
false,
false,
false,
false,
false
],
"holidays": null
}
Holiday Form
{
"holiday1": "2020-01-01",
"holiday2": "2020-03-01",
"holiday3": "2020-02-01"
}
Does anyone have any idea what I might be doing wrong? Thankful,
The problem seems to be you are using holidaysForm before you ever put values in it. So at the point where you are creating the object to loop through to create your array, that object will have no properties.
buildHolidays() {
if (typeof this.valForm !== 'undefined') {
let teste = Object.assign({}, this.holidaysForm.value); // this line
this.holidays = new Array();
Object.values(teste).forEach((v) => {
this.holidays.push(v);
})
console.log(this.holidays);
const values = this.holidays.map((v)=> new FormControl(v));
return this.fb.array(values);
}
}
I would figure out how to get values to use in this method to make it all work. And remember, you can always add a formArray after the creation of your original formgroup via group.addControl('controlName', myFormArray);
Best of luck, and as always
Happy Coding

Updating an array in SharePoint spfx using React & PnPJS

I'm creating a web app that allows the user to update their status and location.
I have a data list table on SharePoint with the user's name, email address, status (for example: online, offline, or busy), location (which is a select field), along with other fields.
The web app is just 2 different select fields. Which allows the user to update his status and location.
When the user accesses the page on componentDidMount() I'm getting the user's email addresses (since he's logged into SharePoint) and then filtering the data list array to view the element for his information (so looking for his email address in the MyList. The part I'm stuck at now is updating the MyList list with the selected response that the user selected.
Using PnP-JS i found this should be possible here are two links showing the update() function.
https://github.com/SharePoint/PnP-JS-Core/wiki/Basic--Operations
https://github.com/SharePoint/PnP-JS-Core/wiki/Working-With:-Items
My code found here:
export default class SigninLocationWebpart extends React.Component<ISigninLocationWebpartProps, {Status: string, Location: string, userName: string, getEmail: string, selectedUser: any}> {
constructor(props) {
super(props);
this.state = {
Status: 'Online',
Location: 'New York',
userName: '',
getEmail: '',
selectedUser: {},
};
this.handleChangeStatus = this.handleChangeStatus.bind(this);
this.handleChangeLocation = this.handleChangeLocation.bind(this);
}
handleChangeStatus(event) {
const { value } = event.target;
this.setState({ Status: value });
}
handleChangeLocation(event) {
const { value } = event.target;
this.setState({ Location: value });
}
private _onUpdate(event) {
event.preventDefault();
//This is where I need help on updating list
let list = pnp.sp.web.lists.getByTitle("MyList")
//Instead of getting by ID i need to get by that selectUser array I believe
list.items.getById(1).update({
Status: this.state.Status, //User changing from Online to Offline
Location: this.state.Location //User changing from New York to Los Angeles
}).then(i => {
console.log(i);
});
}
public componentDidMount() {
if (Environment.type === EnvironmentType.Local) {
}
else if (Environment.type === EnvironmentType.SharePoint || Environment.type === EnvironmentType.ClassicSharePoint) {
//This gets the current users info and sets it to username and email
sp.web.currentUser.get().then((response : CurrentUser) => {
//console.log(response);
this.setState({
userName: response["Title"],
getEmail: response["Email"],
})
})
//This gets the list of all all items in the list
pnp.sp.web.lists.getByTitle("MyList").items.get().then((items: any[]) => {
console.log(items);
//Comparing email from sign in user and filtering items array to get that element
var compareEmail = this.state.getEmail;
console.log(compareEmail);
let selectedUser = _.filter(items, function(item) {
return item.Email_x0020_Address.toLowerCase() === compareEmail.toLowerCase();
});
console.log(selectedUser);
});
}
}
public render(): React.ReactElement<ISigninLocationWebpartProps> {
return (
<div className={ styles.signinLocationWebpart }>
<h3>Hello {this.state.userName}</h3>
<form onSubmit={this._onUpdate}>
<label>
Check In our Out
</label>
<select name="Status" value={this.state.Status} onChange={this.handleChangeStatus}>
<option value="Online">Online</option>
<option value="Offline">Offline</option>
<option value="Busy">Busy</option>
</select>
<label>
Location
</label>
<select name="Location" value={this.state.Location} onChange={this.handleChangeLocation}>
<option value="New York">New York</option>
<option value="Michigan">Michigan</option>
<option value="Los Angeles">Los Angeles</option>
</select>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
First of all, instead of getting all items in the List, and then filtering for the current user, you should get only the item(s) for the current user to begin with. Once you list gets large, you would be performing a lot of overhead by retrieving all items.
Secondly, and what you allude to in your comments, is that you need to specify the ID of the item to update. So, in your componentDidMount, after you get the List Item for the current user, save that Item in your state.
public componentDidMount() {
if (Environment.type === EnvironmentType.Local) {
}
else if (Environment.type === EnvironmentType.SharePoint || Environment.type === EnvironmentType.ClassicSharePoint) {
//This gets the current users info and sets it to username and email
sp.web.currentUser.get().then((response : CurrentUser) => {
//console.log(response);
this.setState({
userName: response["Title"],
getEmail: response["Email"],
});
pnp.sp.web.lists.getByTitle("MyList").items.filter("Email_x0020_Address eq '" + this.state.getEmail + "'").top(1).get().then((items: any[]) => {
if (items && items.length) {
this.setState( { selectedUser: items[0] } );
}
});
})
}
}
Then at update time, you can use the ID of that item to save it.
private _onUpdate(event) {
event.preventDefault();
//This is where I need help on updating list
let list = pnp.sp.web.lists.getByTitle("MyList")
//Instead of getting by ID i need to get by that selectUser array I believe
list.items.getById(this.state.selectedUser.ID).update({
Status: this.state.Status, //User changing from Online to Offline
Location: this.state.Location //User changing from New York to Los Angeles
}).then(i => {
console.log(i);
});
}
Additionally, you'll want to make sure you are binding your submission handler just like you are doing for your onchange handlers in your constructor:
this._onUpdate = this._onUpdate.bind(this);
I will also add, that unless you've make sure to pre-populate the List with all possible users, and will always keep it updated with new users, it would be best in to put a check in your _onUpdate that if this.state.selectedUser == null || this.state.selectedUser.ID == null then you should create a new item (and add the new item to your this.state.selectedUser), instead of updating.

Ag-grid fill row after select in infinite scroll model reactjs

I use ag-grid as infinite scroll model. When i select some row, i check it in BE and after that i want to fill this row as green (in screenshot blue - it's selected row, i want to fill green this row after some action, for example, after click button for checking this row).
I try to set RowClassRules for this way, but it's not worked. But this work before the table waas rendered. After the table was rendered i select row and it's not fill green.
I know about updateData function, but it's not supported in infinite scroll model. Can i do this with another way?
render(){
let cells = this.state.rowIndexWithBadValue;
let cellsImported = this.state.rowIndexAlreadyImported;
return(
...
<AgGridReact
enableColResize={true}
columnDefs={this.state.columnDefs}
rowModelType="infinite"
rowSelection="multiple"
rowDeselection={true}
maxBlocksInCache={2}
suppressRowClickSelection={true}
getRowNodeId={this.state.getRowNodeId}
datasource={this.getDataSource(1000)}
isRowSelectable={this.state.isRowSelectable}
rowClassRules={{
"red-row": function(params) {
return cells.find(e => e === params.node.rowIndex) !== undefined ? true : false;
},
"green-row": function(params) {
return cellsImported.find(e => e === params.node.id) !== undefined ? true : false;
},
}}
onGridReady={this.onGridReady}
onSelectionChanged={this.onSelectionChanged}
/>
...
)
}
State:
this.state = {
columnDefs: this.props.columnDefs,
data: this.props.data,
selectedData: null,
getRowNodeId: function(item) {
let columnIndex = null;
Object.keys(item).map((elem, index) => {
if (elem === item_id) { columnIndex = index; }
});
return Object.values(item)[columnIndex];
},
rowIndexWithBadValue: this.props.rowIndexWithBadValue,
isRowSelectable: function(rowNode) {
return row.find(e => e === rowNode.rowIndex) == undefined ? true :false;
},
jumpButton: true,
selectButton: false,
deselectButton: false,
primaryKey: this.props.primaryKey,
nextBadRow: null,
columnsWithDefaultsvalues: this.props.columnsWithDefaultsvalues,
rowIndexAlreadyImported: this.props.rowIndexAlreadyImported
};
For this case you don't have full solution.
You can make only one: fill this rows, but after rerendering it lose.
So you can only prepare data before first render of table, or you can change source data and rerender table, but in this case you lose all selected rows and you need to set it selected again.

Checkbox not being checked using VueJS 2

I use Axios to GET data from my server. Basically, what I want to achieve is use the response from the GET request and get the data to set the checkbox to true or false depending on the response.
But the problem is, it does not set the checkbox to true or false. But rather, the value of this.checked will always be "".
Here is my code:
<template>
<input type="checkbox" value="Yes" v-model="checked">Yes</label>
</template>
export default {
data () {
return {
checked: ''
}
}
...
...
created () {
...
...
if ((response.data.categoryTypeId) === noSubCat) {
// checkbox is not checked
this.checked === false
} else {
// checkbox is checked
this.checked === true
}
}
}
You should assigne the value, instead you're doing a comparisation with ===
created () {
...
...
if ((response.data.categoryTypeId) === noSubCat) {
// checkbox is not checked
// this.checked === false // this is a comparison
// I guess is assign what you want.
this.checked = false
} else {
// checkbox is checked
this.checked = true
}
}
}
And you can simplify the code above to something like this:
this.checkbox = (response.data.categoryTypeId) === noSubCat ? false : true;

Resources