How to validate react input fields with some custom validation - javascript

I am working with react and making some new input fields dynamically, and using React-gook-form for validation.
What I am doing is -
I have one dropdown in which I have numbers as dropdown, like 1,2,3,4
So which ever dropdown user is selecting I am creating that much input field
if user selects 3 then 3 input fields
What I am trying to achieve is
When user create two input fields i do not want both of them to have same input.
Like if first input is having D as input then the second one should not take D in second one.
PS- here just for minimal code i am creating only one set of input, like first name
It can be when i select 2 then I can create two set of inputs having Fname and Lname in each set so there both fname should not take same value and same goes for lname.
What I have done
this one to hold the state, initial one is selected and one input is created.
const [initialSet, setinitialSet] = useState(1);
On select change doing this
const Select_change = (e) => {
let val = parseInt(e.target.value);
setinitialSet(val);
};
Now using that initial state to loop my elements to create input fields
<div className="row">
{[...Array(initialSet).keys()].map((li, index) => (
<div className="col-12 col-sm-12 col-md-8 col-lg-4 col-xl-4">
<div className="form-group">
<input
type="text"
name={`data[${index}].name`}
id={"name" + index}
className="form-control"
placeholder="F Name"
ref={register({ required: `F name ${index} is required` })}
/>
<label" htmlFor={"name" + index}>
F Name
</label>
{errors.data && errors.data[index] && errors.data[index].name && (
<div>
<span className="text-danger">
{errors.data[index].name.message}
</span>
</div>
)}
</div>
</div>
))}
</div>
Now I just want to validate the fname so that two input fields of f name should not have same input.
Code sandbox link
I have full working code in my code sandbox

You can use an array of inputs so when you select 3 you create an array with 3 elements that will contain the data.
const [initialSet, setinitialSet] = useState([]);
and here's the creation process:
const Select_change = (e) => {
let val = parseInt(e.target.value);
setinitialSet( new Array(val));
}
optionally you can initialise the array to zeros if you want
setinitialSet( new Array(val).fill(0) );
When you loop over the inputs you will use the index of the element in the array.
You can even store names and errors in each element of the element if you want a complicated logic.

Related

How do I add input values to an object and display the results in React?

I'm working on a CV Application in React and part of the process is adding your previous Education and displaying it so a potential employer can see it. I have an input set up so users can enter information and upon hitting save, the values are sent and displayed in a separate div.
This form also only shows up when I click the +Add Education button at the bottom.
<form>
<label htmlFor="school">School</label>
<input type="text" name="school" onChange={(e) => this.setSchool(e.target.value)} />
<label htmlFor="study">Field of Study</label>
<input type="text" name="study" onChange={(e) => this.setStudy(e.target.value)} />
<button onClick={this.onSubmit} className="save">Save</button>
</form>
<button onClick={this.toggleForm} className="form-btn">
+ Add Education
</button>
I also have this onChange event that's attached to each input which takes the values from each input and displays them to a div.
onChange={(e) => this.setSchool(e.target.value)}
Here is the code I'm using for both setSchool and setStudy.
class Education extends Component {
constructor(props) {
super(props);
this.state = {
school: "",
study: "",
showOutput: false,
};
setSchool = (value) => {
this.setState({ school: value });
};
setStudy = (value) => {
this.setState({ study: value });
};
I also have this code which is placed above my other jsx code.
render() {
return (
<>
<div>
{this.state.showOutput && (
<div className="experience">
<p>{`School: ${this.state.school}`}</p>
<p>{`Field of Study: ${this.state.study}`}</p>
</div>
)}
This code exists for the purpose of displaying only when the setSchool and setStudy values aren't empty. When the user fills them out and clicks save, it displays in a div above the form.
Now here's where I need help
Everything about this code works as intended. However, when I go to click +Add Education for the second time, the values are simply being overriden. So instead of having two separate divs like this:
Div 1
School: University of Test
Field of Study: Comp. Sci
Div 2
School: University of Test 2
Field of Study: Comp. Sci 2
I'm only getting one because the second div of inputted information is overriding the first.
School: University of Test 2
Field of Study: Comp. Sci 2
I've been trying so solve this issue and the only thing I can think of is to add each input value to an object rather than just adding it to setState:
const study = [
{
school: school,
fieldofStudy: fieldofStudy,
},
];
But I can't seem to figure out how to add the values to this object and then display the results. I know if I displayed the results by looping through the array instead of e.target.value I could get them to show up but everything I've tried thus far hasn't worked.
Add a state array to keep track of your school/study pairs (this.state.education, for example) and then add them with the button is pressed like:
setEducation = () =>{
this.setState((state)=>({education: [...state.education, {study: this.state.study, school: this.state.school}]}))
And then just loop through this.state.education in your render

Reactive form validation for dynamic and hidden fields

First off, I have an Angular reactive form that has a button that can add another FormArray to the form. All validation good and working as expected. Things have gotten a little tricky when introducing another dynamic control to the already dynamic form group. This control is shown/hidden based on a selection made in another form control.
When the control is shown I introduce validation, when the control is hidden the validation is cleared. This ensures that my form remains valid/invalid correctly.
Its acting a little buggy e.g. when I complete a group of inputs and add another dynamic group of inputs, both triggering the hidden control... then to amend the previous hidden input - the form remains true. e.g.
Selecting 123 triggers the "Other Code" control, removing the value should make the form invalid, but it stays true at this point.
I have a change function assigned to the select to determine whats been selected.
selectClick(x) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls.forEach(i => {
console.log(i)
i['controls'].other.setValidators([Validators.required]);
// i['controls'].other.updateValueAndValidity();
});
} else {
items.controls.forEach(i => {
i['controls'].other.clearValidators();
// i['controls'].other.updateValueAndValidity();
});
}
f.updateValueAndValidity();
}
I suspect when changing the select property to trigger the above it does not do it to the correct index item, and it does it for all?
StackBlitz - https://stackblitz.com/edit/angular-prepopulate-dynamic-reactive-form-array-ufxsf9?file=src/app/app.component.ts
the best way to "clear/add Validators" really is enabled or disabled the formControls. Remember a formControl has as status one of this:
type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
So we can simple enabled/disabled the FormControl. Futhermore, when we create the formGroup we can created disabled, so at first will be not INVALID
Well, the code is a bit confussed. first the use of i['controls'].other (really you can use i.controls.other an a estrange mix using FormBuilder and new Form.
As always we has a FormArray we create a getter
get justificationItems()
{
return this.form.get('justificationItems') as FormArray;
}
In stead use two differents functions to create the form, we can use and unique
createJustificationField(x: any=null): FormGroup {
x=x || {name:null,description:null,code:null,other:null}
return new FormGroup({
name: new FormControl(x.name, [Validators.required]),
description: new FormControl(x.description, [Validators.required]),
code: new FormControl(x.code, [Validators.required]),
other: new FormControl({value:x.other,
disabled:x.code!=='123'},[Validators.required]),
});
}
See that we can use as
this.createJustificationField(..an object..)
this.createJustificationField()
Our functions: createForm, addItem and selectClick (I like more another name like codeChange but is a minor change) becomes like
createForm() {
this.service.getmodel().subscribe((response:any) => {
this.form = new FormGroup({
justificationItems: new FormArray(
response.justificationItems.map(x=>
this.createJustificationField(x))
),
});
});
}
addItem(): void {
this.justificationItems.push(this.createJustificationField());
this.form.updateValueAndValidity();
}
selectClick(x,i) {
if (x === '123')
this.justificationItems.at(i).get('other').enable()
else
this.justificationItems.at(i).get('other').disable()
this.form.updateValueAndValidity();
}
And the .html becomes more clear in the way
<form *ngIf="form" [formGroup]="form">
<div formArrayName="justificationItems">
<div
*ngFor="
let orgs of justificationItems.controls;
let i = index;
let last = last
"
[formGroupName]="i"
>
<label>Name </label>
<input formControlName="name" placeholder="Item name" /><br />
<label>Description </label>
<input
formControlName="description"
placeholder="Item description"
/><br />
<label>Code </label>
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>
<option value="123">123</option>
<option value="456">456</option></select
><br />
<ng-container *ngIf="justificationItems.at(i).value.code === '123'">
<label>Other Code </label>
<input formControlName="other" placeholder="other" /><br /><br />
</ng-container>
<button
*ngIf="last"
[disabled]="justificationItems.at(i).invalid"
type="button"
(click)="addItem()"
>
Add Item
</button>
</div>
</div>
<button [disabled]="!form.valid" type="button">Submit</button>
</form>
<p>Is form valid: {{ form?.valid | json }}</p>
see the stackblitz
Root cause: When selectClick trigger, you clear or set validation for all controls other in array form. You should set only for one form in formArray.
I rewrite your function:
selectClick(x, index) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls[index]['controls'].other.setValidators([Validators.required]);
} else {
items.controls.forEach(i => {
items.controls[index]['controls'].other.clearValidators();
i['controls'].other.updateValueAndValidity();
});
}
items.controls[index]['controls'].other.updateValueAndValidity();
}
change code in template:
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>

React Formik get form state's values

I have a form with two fields name and age. I know when user fill these two fields and hit the submit button I can get the values for these two fields.
But what I want is, I have a button call show filled values. When your press that button I want to see the user-filled values in the form. For example, if he fills name as 'Tom' and age as 85. I want to see that. But right now what I am getting is the empty initial values object.
My Code
How do I achieve this using Reactjs?
U can get the filled fields using Formiks props provided by the Formik.
The values prop provided by the formik will gives the filled fields values. Initially it will be the empty object . After you start filling the fields, the values object will be updated accordingly.
To know more What Props can Formik provide ?
visit :- https://formik.org/docs/api/formik#touched--field-string-boolean-
Hope the below code snippet will be useful for you.
<Formik
initialValues={{}}
validationSchema={() => Yup.object().shape({`enter your validation keys here`})}
onSubmit={(values) => {
console.log('values after submit', values);
}}
>
{(formikProps) => {
const { values, handleChange } = formikProps;
return (
<Form>
<label>
name
<input name="name" onChange={handleChange} />
</label>
<label>
age
<input name="age" type="number" onChange={handleChange} />
</label>
<button
type="button"
onClick={() => {
console.log('show filled fields', values);
}}
>
Show Filled Fields
</button>
<button type={'submit'}> Submit</button>
</Form>
);
}}
</Formik>

Input validation - react form submitting without any values

I'm having an input validation problem thats allowing the form to submit without having any selectorValues added. The check I have seems to only check for input inside the textarea but doesn't account for the Add button being pressed.
Here's a sandbox reproducing the issue.
I'm using Semantic-ui-react so my <Form.Field /> looks like this:
<Form.Field required>
<label>Selector Values:</label>
<TextArea
type="text"
placeholder="Enter selector values 1-by-1 or as a comma seperated list."
value={this.state.selectorValue}
onChange={this.handleSelectorValueChange}
required={!this.state.selectorValues.length}
/>
<Button positive fluid onClick={this.addSelectorValue}>
Add
</Button>
<ul>
{this.state.selectorValues.map((value, index) => {
return (
<Card>
<Card.Content>
{value}
<Button
size="mini"
compact
floated="right"
basic
color="red"
onClick={this.removeSelectorValue.bind(this, index)}
>
X
</Button>
</Card.Content>
</Card>
);
})}
</ul>
</Form.Field>
So in the above, <TextArea> has a required prop: !this.state.selectorValues.length. This is only checking for input inside the textarea, it should check that the value has been added by pressing the Add button before allowing the form to submit.
In your addSelectorValue add a check to see if this.state.selectorValue it not empty, if it is just return, this will prevent adding empty values to selectorValues
addSelectorValue = e => {
e.stopPropagation();
e.preventDefault();
if (!this.state.selectorValue) return;
//continue if this.state.selectorValue has a value
};
Before submitting add a check to see if this.selectorValues is empty, if so focus on textarea.
To focus we need to first create a ref to our textarea.
Create a ref to be
attached to a dom element
textareaRef = React.createRef();
// will use our ref to focus the element
focusTextarea = () => {
this.textareaRef.current.focus();
}
handleSubmit = () => {
const { selectorValues } = this.state;
if (!selectorValues.length) {
// call our focusTextarea function when selectorValues is empty
this.focusTextarea();
return;
}
this.setState({ submittedSelectorValues: selectorValues });
};
// attach our ref to Textarea
<Textarea ref={this.textareaRef} />
After some search ... required prop is for decorational purposes only - adding astrisk to field label.
It has nothing to form validation. You need a separate solution for that - try formik or set some condition within submit handler.
Formik plays nicely with yup validation schema - suitable for more complex, dynamic requirements.

Can't bind to dynamically added input in Bootstrap modal of Angular component

First of all, I don't see how the modal could have anything to do with this issue since its actually in this component's code, not a child. Still, this is in a modal, just in case.
I'm opting to not use FormArray since I need to keep track of my selections that may be added in a separate object, so I'm just creating unique IDs for the controls and adding them to the FormGroup. I can access the controls in the ts code, set values, get values, etc, but the form isn't binding and I can't figure out why not.
I can have an unknown number of items in this modal form, which will each have a selector (dropdown to select a property) and then the input to be able to modify some data. The input could be of different types, so it needs to be added and binded upon the choice from the selector.
<form [formGroup]="multiEditFormGroup" novalidate>
<div *ngFor="let item of multiEditSelections; let i = index">
<div>
<mdb-select [options]="availablePropsSelect"
placeholder="Choose property to edit"
class="colorful-select dropdown-main"
(selected)="multiEditSelectProp(item.selectorId, $event)"></mdb-select>
<label>Property to edit</label>
</div>
<div>
<div>
<input mdbActive
type="text"
class="form-control"
[formControlName]="item.controlId" />
</div>
</div>
</div>
</form>
Excepts of ts code:
public multiEditFormGroup = new FormGroup({});
onModalOpen():void {
const selectorId = this.utils.uuid();
this.multiEditFormGroup.addControl(selectorId, this.propSelector);
this.multiEditSelections.push({
selectorId: selectorId,
prop: '',
label: '',
type: '',
controlId: '' // not yet known since type hasn't been chosen
});
}
onSelect(selectorId: string, selectEvent: any): void {
let selection = this.multiEditSelections.find(selection => {
return selection.selectorId === selectorId;
});
const controlId = this.utils.uuid();
const prop = selectEvent.value;
this.multiEditFormGroup.get(selection.selectorId).setValue(prop);
this.multiEditFormGroup.markAsDirty();
this.multiEditFormGroup.markAsTouched();
const model = this.multiEditModel.find(model => {
return model.prop === prop;
});
this.multiEditFormGroup.addControl(controlId, this.testCtrl);
selection.controlId = controlId;
selection.prop = prop;
selection.label = model.label;
selection.type = model.type;
}
Logging to console shows that items are being added to the FormGroup, but the binding isn't happening to the DOM. For example, I can add a (keyup) event handler to my input and set the value in the form control which has already been created, and the FormGroup is updated. However, any input added in the front-end doesn't update the FG since it obviously isn't binding. Is this a timing issue or because the controlId is being updated later? I'm creating the FormControl before updating my array that is being iterated.
Oh and I get no errors in console on this.
I think you need to make this change:
[formControlName]="item.controlId"
needs to be:
formControlName="{{item.controlId}}"

Categories