How to access form inputs in react js with nested object - forms

I'm working a react form using the controlled inputs to access the change on the form inputs. It contained lot of fields but i haven't posted all the fields.
import React from 'react';
import Links from './Links.jsx';
import axios from 'axios';
import Dialog from 'react-bootstrap-dialog';
import {Typeahead} from 'react-bootstrap-typeahead';
import Autocomplete from 'react-autocomplete';
import style from './app.css';
class AddNewEmployee extends React.Component {
constructor(props){
super(props);
this.state = {
firstName : '',
middleName : ''
}
this.handleUserInput = this.handleUserInput.bind(this);
}
handleUserInput(){
const name = e.target.name;
const value = e.target.value;
this.setState({
[name]: value
})
}
render() {
return(
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="firstName"
value={this.state.firstName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="middleName"
value={this.state.middleName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
)
}
}
export default AddNewEmployee;
The above code is working fine so far, but i got a requirement to keep an nested object to save the form input values.
But i'm missing some thing here how to handle the user input on form elements.
How shall we handle the user input when we have a nested object.
import React from 'react';
import Links from './Links.jsx';
import axios from 'axios';
import Dialog from 'react-bootstrap-dialog';
import {Typeahead} from 'react-bootstrap-typeahead';
import Autocomplete from 'react-autocomplete';
import style from './app.css';
class AddNewEmployee extends React.Component {
constructor(props){
super(props);
this.state = {
employee : {
firstName : '',
middleName : ''
}
}
this.handleUserInput = this.handleUserInput.bind(this);
}
handleUserInput(){
// here how to handle those input changes
}
render() {
return(
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="firstName"
value={this.state.employee.firstName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="middleName"
value={this.state.employee.middleName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
)
}
}
export default AddNewEmployee;

handleUserInput(e) {
// Option #1 (mutable data)
let {employee} = this.state;
const name = e.target.name;
const value = e.target.value;
employee[name] = value;
this.setState({
employee
});
// Option #2 (immutable data)
const { employee } = this.state;
const name = e.target.name;
const value = e.target.value;
this.setState({
employee: {
...employee,
[name] : value
}
});
}
Also, since you are binding handleUserInput in the constructor, you can simplify onChange={(event) => this.handleUserInput(event)} to onChange={this.handleUserInput}

Related

Angular 6 asynchronous autocomplete not working but it displaying items without changing value

Angular 6 asynchronous autocomplete is not working but it displays items without changing value and does not reduce the list of proposed values
component.ts:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators,FormControl} from '#angular/forms'
import { Observable } from 'rxjs';
import { startWith, map } from 'rxjs/operators';
export class AjouterMarcheComponent implements OnInit {
createForm: FormGroup;
myControl : FormControl;
directions: string[] = ['DGI','SSI','TTU','BLI'];
filteredDirections: Observable<string[]>;
constructor(private fb: FormBuilder) {
this.createForm = this.fb.group({
NomDirection: ['', Validators.required]})
this.myControl= new FormControl();
});
}
ngOnInit() {
this.filteredDirections = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase(); //miniscule
return this.directions.filter(direction => `direction.toLowerCase().indexOf(filterValue) === 0);
}
}
Thanks for your help
Compoment.html
compoment.html
<div>
<br>
<mat-card>
<br>
<form [formGroup]="createForm" class="create-form" >
<mat-form-field class="field-full-width">
<input placeholder="Choisir ou ajouter une direction" type="text" aria-label="Number" [formControl]="myControl" matInput [matAutocomplete]="auto" #NomDirection>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options" [value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
</mat-card>
</div>

Redux-form get the value from other tab/component

I have to implement a form with a bit less than 30 different fields.
So I decided to split them in 2 differents container component with two tabs to navigate between them.
I use redux-form to handle the data binding.
For on component I can get the value from handleSubimit of one component. But the final validation must be in the last tab only. From here I only have access to the value of the second tab. Like the data from the store where wipe out.
How can I access the store where my previous data should be ?
TabNavigationBar.js
import React from 'react';
import TabNavigationItem from './TabNavigationItem';
const TabNavigationBar = ({ onTabChange, activeTab }) => {
const tabList = [
{ hasIcon: 'fas fa-user-circle', hastext: 'Information Utilisateur' },
{ hasIcon: 'fas fa-file-alt', hastext: 'Informations contrat' }
];
const clickOnTab = tabNumer => {
onTabChange(tabNumer);
};
return (
<div className="columns">
<div className="column is-offset-one-quarter-desktop is-offset-one-thirds-tablet is-half-desktop is-one-thirds-tablet">
<div className="tabs is-toggle is-fullwidth">
<ul>
{tabList.map((tab, i) => (
<TabNavigationItem
key={i}
tabSelected={() => clickOnTab(i)}
hasClass={activeTab === i ? 'is-active' : ''}
hasIcon={tab.hasIcon}
hasText={tab.hastext}
/>
))}
</ul>
</div>
</div>
</div>
);
};
export default TabNavigationBar;
UserForm.js
import React from 'react';
import { reduxForm } from 'redux-form';
import CiviliteRadioButton from './CiviliteRadioButton';
import NameInputs from './NameInputs';
import AddressInputs from './AddressInputs';
import MailAndDOB from './MailAndDOB';
import TelephoneInputs from './TelephoneInputs';
let UserForm = ({ handleSubmit }) => {
return (
<div>
<CiviliteRadioButton />
<NameInputs />
<AddressInputs />
<MailAndDOB />
<TelephoneInputs />
</div>
);
};
UserForm = reduxForm({
form: 'form1',
initialValues: {
user: {
adresse: {
country: 'France'
},
civilite: 'Madame'
}
}
})(UserForm);
export default UserForm;
ContractForm.js
import React from 'react';
import { reduxForm } from 'redux-form';
import InputItem from '../InputItem';
import ContratInputsList from './contratInputList';
let ContratForm = ({ handleSubmit }) => {
const submit = values => {
console.log(values);
};
return (
<div>
<div className="columns is-multiline ">
{ContratInputsList.map((item, i) => {
return (
<div className="column is-half" key={i}>
<InputItem spec={item.spec} />
</div>
);
})}
</div>
<div className="columns">
<div className="column">
<div className="field is-grouped is-grouped-right">
<input
className="button is-primary"
onClick={handleSubmit(submit)}
type="submit"
value="Envoyer"
/>
</div>
</div>
</div>
</div>
);
};
ContratForm = reduxForm({
form: 'form2'
})(ContratForm);
export default ContratForm;
EDIT
When I click on my tabs, redux-form/DESTROY is called and erase form1's data.
Try setting destroyOnUnmount flag to false in reduxForm(options).

Pass new password value to validator.ts in Angular 4

I am working through the challenges for a Udemy course on Angular 4, but I am stuck on a challenge where I have to create an input for a new password and then another input to confirm the new password using reactive forms.
I have an external .ts file called password.validators.ts that has custom form validation code, and I can get the value of the currently selected input box by passing a control object with AbstractControl, but how do I pass a value to my component.ts file and then from my component.ts to my password.validators.ts ? I need to be able to compare the new password value to the confirm password value and I'm stuck!
new-password.component.html
<form [formGroup]="form">
<div class="form-group">
<label for="oldPassword">Old Password</label>
<input
formControlName="oldPassword"
id="oldPassword"
type="text"
class="form-control">
<div *ngIf="oldPassword.pending">Checking password...</div>
<div *ngIf="oldPassword.touched && oldPassword.invalid" class="alert alert-danger">
<div *ngIf="oldPassword.errors.required">Old password is required</div>
<div *ngIf="oldPassword.errors.checkOldPassword">Password is incorrect</div>
</div>
</div>
<div class="form-group">
<label for="newPassword">New password</label>
<input
formControlName="newPassword"
id="newPassword"
type="text"
class="form-control">
<div *ngIf="newPassword.touched && newPassword.invalid" class="alert alert-danger">
<div *ngIf="newPassword.errors.required">New password is required</div>
</div>
</div>
<div class="form-group">
<label for="confirmNewPassword">Confirm new password</label>
<input
formControlName="confirmNewPassword"
id="confirmNewPassword"
type="text"
class="form-control">
<div *ngIf="confirmNewPassword.touched && confirmNewPassword.invalid" class="alert alert-danger">
<div *ngIf="confirmNewPassword.errors.required">Confirm password is required</div>
<div *ngIf="confirmNewPassword.errors.confirmNewPassword">Passwords don't match</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Change Password</button>
</form>
new-password.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { PasswordValidators } from './password.validators';
#Component({
selector: 'new-password',
templateUrl: './new-password.component.html',
styleUrls: ['./new-password.component.css']
})
export class NewPasswordComponent {
form = new FormGroup({
oldPassword: new FormControl('', Validators.required, PasswordValidators.checkOldPassword),
newPassword: new FormControl('', Validators.required),
confirmNewPassword: new FormControl('', Validators.required )
})
get oldPassword() {
return this.form.get('oldPassword');
}
get newPassword() {
return this.form.get('newPassword');
}
get confirmNewPassword() {
return this.form.get('confirmNewPassword');
}
addNewPassword(newPassword: HTMLInputElement) {
let np = this.newPassword;
return np;
}
}
password.validators.ts
import { AbstractControl, ValidationErrors } from '#angular/forms';
export class PasswordValidators {
static checkOldPassword(control: AbstractControl) : Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(control.value !== '1234')
resolve({ checkOldPassword: true }) ;
else resolve(null);
}, 2000);
});
}
static confirmNewPassword(control: AbstractControl) : ValidationErrors | null {
if(control.value === control.newPassword.value)
return null;
}
}
I have used the following code for my password validation may be this can help you
In password.validator write this code
import {AbstractControl} from '#angular/forms';
export class PasswordValidation {
static MatchPassword(AC: AbstractControl) {
let password = AC.get('password').value;
let confirmPassword = AC.get('confirmPassword').value;
if(password != confirmPassword) {
console.log('false');
AC.get('confirmPassword').setErrors( {MatchPassword: true} )
} else {
console.log('true');
return null
}
}
}
and in the component file use this code
constructor(fb: FormBuilder)
{
this.form = fb.group({
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, {
validator: PasswordValidation.MatchPassword // your validation method
})
}
and in html file to find error use this code
<div class="alert alert-danger" *ngIf="form.controls.confirmPassword.errors?.MutchPassword">Password not match</div>
Hope it would help you

Angular2: issues converting a form's value to a Model with default fields

I am busy with a Stock take form that needs to be posted to a REST API. I have a stockTake model which has three fields that get initialized with default values of 0. the three fields "StockTakeID, IDKey and PackSize" are not part of the angular form where the data needs to be entered. My issue is submitting the model with those three fields with default values to my RestService. When I submit the stockTakeForm.value I get an error as those three fields are not part of the data that's being submitted... Any idea how I can go about getting this to work?
my stock-take.model.ts:
export class StockTakeModel {
constructor(
StockTakeID: number = 0,
IDKey: number = 0,
BarCode: number,
ProductCode: string,
SheetNo: string,
BinNo: string,
Quantity: number,
PackSize: number = 0) { }
}
my stock-take.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { RestService } from '../../services/rest.service';
import { StockTakeModel } from '../../models/stock-take.model';
#Component({
selector: 'stock-take',
templateUrl: './stock-take.component.html',
styleUrls: ['./stock-take.component.css']
})
export class StockTakeComponent implements OnInit {
stockTakeForm: FormGroup;
constructor(private fb: FormBuilder, private restService: RestService) {
this.stockTakeForm = fb.group({
'sheetNo':['', Validators.required],
'binNo':['', Validators.required],
'barcode':['', Validators.required],
'Qty':['', Validators.required]
});
}
submitStockTake(stockTakeModel: StockTakeModel) {
//console.log(stockTakeModel);
this.restService.postStockTake(stockTakeModel)
.subscribe(
(res) => {
console.log(res);
},
(res) => {
console.log("failure: " + res);
}
);
this.stockTakeForm.reset();
}
ngOnInit() {
}
}
my stock-take.component.html:
<div class="row">
<div class="col-md-6 col-md-push-3">
<h1>Stock Take</h1>
<br /><br />
<form [formGroup]="stockTakeForm" role="form" (ngSubmit)="submitStockTake(stockTakeForm.value)">
<input type="text" placeholder="Enter Sheet Number" class="form-control" id="sheetNo" [formControl]="stockTakeForm.controls['sheetNo']" name="sheetNo">
<input type="text" placeholder="Enter Bin Number" class="form-control" id="binNo" [formControl]="stockTakeForm.controls['binNo']" name="binNo">
<input type="number" placeholder="Enter Barcode" class="form-control" id="bCode" [formControl]="stockTakeForm.controls['barcode']" name="barcode">
<input type="number" placeholder="Enter Quantity" clas="form-control" id="Qty" [formControl]="stockTakeForm.controls['Qty']" name="quantity">
<button class="btn btn-block" [disabled]="!stockTakeForm.valid">Submit</button>
</form>
</div>
</div>
updated stock-take.component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { RestService } from '../../services/rest.service';
import { StockTakeModel } from '../../models/stock-take.model';
#Component({
selector: 'stock-take',
templateUrl: './stock-take.component.html',
styleUrls: ['./stock-take.component.css']
})
export class StockTakeComponent implements OnInit {
stockTakeForm: FormGroup;
stockTakeModel: StockTakeModel;
constructor(private fb: FormBuilder, private restService: RestService) {
this.stockTakeForm = fb.group({
'sheetNo':['', Validators.required],
'binNo':['', Validators.required],
'barcode':['', Validators.required],
'Qty':['', Validators.required]
});
}
doStockTake(val: any) {
//console.log("val:" + JSON.stringify(val));
this.stockTakeModel = new StockTakeModel(0, 0, val[Object.keys(val)[2]], '', val[Object.keys(val)[0]], val[Object.keys(val)[1]], val[Object.keys(val)[3]], 0);
// console.log(this.stockTakeModel);
console.log(JSON.stringify(this.stockTakeModel));
}
submitStockTake(stockTakeModel: StockTakeModel) {
//console.log(stockTakeModel);
this.restService.postStockTake(stockTakeModel)
.subscribe(
(res) => {
console.log(res);
},
(res) => {
console.log("failure: " + res);
}
);
this.stockTakeForm.reset();
}
ngOnInit() {
}
}
One workaround would to create a new variable, e.g:
stockTakeModel: StockTakeModel;
if that doesn't work (as you said it complained about undefined), you can also instantiate it as an empty Object, like so:
stockTakeModel = {};
EDIT, so you changed your code to something like this: I also added some console.logs to show where it is undefined, as there was a little question about that.
stockTakeModel: StockTakeModel; // undefined at this point (or empty if you have defined it as empty above)!
// use Object keys to access the keys in the val object, a bit messy, but will work!
submitStockTake(val: any) {
this.stockTakeModel =
new StockTakeModel(0, 0, val[Object.keys(val)[2]], '', val[Object.keys(val)[0]],
val[Object.keys(val)[1]], val[Object.keys(val)[3]]);
// console.log(this.StockTakeModel); // now it should have all values!
this.restService.postStockTake(this.stockTakeModel)
.subscribe(d => { console.log(d)} ) // just shortened your code a bit
this.stockTakeForm.reset();
}
EDIT: So finally the last issue is solved, besides the solution above. It's because your StockTakeModel-class wasn't properly declared the value was undefined, or actually empty. So fix your class to:
export class StockTakeModel {
constructor(
public StockTakeID: number = 0, // you were missin 'public' on everyone
public IDKey: number = 0,
public BarCode: number,
public ProductCode: string,
public SheetNo: string,
public BinNo: string,
public Quantity: number,
public PackSize: number = 0) { }
}
Note also that in your 'updated' code you still have (ngSubmit)="submitStockTake(stockTakeForm.value) in your form, so your created doStockTake-method is not called.
I don't understand the construction [formControl] in your template.
There should be at least [ngModel].
Usualy I use, for two-way communication, the [(ngModel)]. Not the [formControl].
So your template code should look like this:
<div class="row">
<div class="col-md-6 col-md-push-3">
<h1>Stock Take</h1>
<br /><br />
<form [formGroup]="stockTakeForm" role="form" (ngSubmit)="submitStockTake(stockTakeForm.value)">
<input type="text" placeholder="Enter Sheet Number" class="form-control" id="sheetNo" [(ngModel)]="stockTakeForm.controls['sheetNo']" name="sheetNo">
<input type="text" placeholder="Enter Bin Number" class="form-control" id="binNo" [(ngModel)]="stockTakeForm.controls['binNo']" name="binNo">
<input type="number" placeholder="Enter Barcode" class="form-control" id="bCode" [(ngModel)]="stockTakeForm.controls['barcode']" name="barcode">
<input type="number" placeholder="Enter Quantity" clas="form-control" id="Qty" [(ngModel)]="stockTakeForm.controls['Qty']" name="quantity">
<button class="btn btn-block" [disabled]="!stockTakeForm.valid">Submit</button>
</form>
</div>
</div>

React updating state in two input fields from form submission

I am trying to make a simple contact form using React. Eventually I will send the data collected from the state to a database, but for right now I am trying to just get it to console log the correct values.
Right now, the email field overrides the name field and when I console log both states, name shows up and email is undefined. Here is my React Component
import React, { Component, PropTypes } from 'react';
import ContactData from '../data/ContactData.js';
class FormContact extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
email: '',
textArea: ''
}
}
handleChange(event) {
event.preventDefault();
this.setState({
name: event.target.value,
email: event.target.email
})
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state.name + ' ' + this.state.email);
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label> Name:
<input type="text" placeholder="Name" value={this.state.name} onChange={this.handleChange.bind(this)} />
</label><br />
<label> Email:
<input type="text" placeholder="Email" value={this.state.email} onChange={this.handleChange.bind(this)}/>
</label><br />
<input className="btn btn-primary" type="submit" value="Submit" />
</form>
)
}
}
FormContact.PropTypes = {
subName: PropTypes.string,
subEmail: PropTypes.string
}
FormContact.defaultProps = {
subName: 'Sam',
subEmail: ''
}
class Contact extends Component {
render() {
return (
<div>
<h1>CONTACT PAGE</h1>
<FormContact />
</div>
)
}
}
export default Contact;
If I understand what you want, you could do it as follows :
Add an empty object in your state for the form values
formValues: {}
Add the name attribute to your fields
<input name="name" .... />
<input name="email" .... />
then depending on that name update your state in handleChange function
let formValues = this.state.formValues;
let name = event.target.name; // Field name
let value = event.target.value; // Field value
formValues[name] = value;
this.setState({formValues})
And if the values go one level deeper, you could use
value={this.state.formValues["name"]} instead of value={this.state.name} - where name is the value of the name attribute of your input field
Thus, everything together should be as follows :
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
formValues: {}
}
}
handleChange(event) {
event.preventDefault();
let formValues = this.state.formValues;
let name = event.target.name;
let value = event.target.value;
formValues[name] = value;
this.setState({formValues})
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state.formValues);
}
render(){
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label> Name:
<input type="text" name="name" placeholder="Name" value={this.state.formValues["name"]} onChange={this.handleChange.bind(this)} />
</label><br />
<label> Email:
<input type="text" name="email" placeholder="Email" value={this.state.formValues["email"]} onChange={this.handleChange.bind(this)}/>
</label><br />
<input className="btn btn-primary" type="submit" value="Submit" />
</form>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is fiddle.
Hope this helps.
The reference to event.target.email does not exist on the event element. The value of a text input from an inline-event handler would be event.target.value for both email and name. The quick solution is to create a separate handler for each input:
handleChangeName(event) {
event.preventDefault();
this.setState({ name: event.target.value }); //<-- both use the same reference
} // to get the contextual value
handleChangeEmail(event) { // from the inputs v
event.preventDefault(); // |
this.setState({ email: event.target.value }); //<--------------------
}