Im trying to map a state property as value to a generated form input with react, but have been unsuccessful so far. The example below shows what im trying to do, but the wrong way as group.value is imported as a string, and wont be compiled into the value of the state variable. Does anyone know how to accomplish this? I need both the final object and property to be dynamically called (in this example frame and brand/year/model)
Map function:
return frameData.map((group) => {
return (
<div className="form-group">
<div className="col-sm-8">
<input
type="text"
className="form-control"
id={group.name}
name={group.name}
placeholder={group.placeHolder}
value={group.value}
onChange={(event) => this.updateValue(event)}
/>
</div>
</div>
);
});
dataset
const frameData = [
{
name: "frame-brand",
placeHolder: "Brand",
value: "{this.state.newSubmission.frame.brand}",
},
{
name: "frame-year",
placeHolder: "Year",
value: "{this.state.newSubmission.frame.year}",
},
{
name: "frame-model",
placeHolder: "Model",
value: "{this.state.newSubmission.frame.model}",
},
];
state
this.state = {
newSubmission:
frame: {
year: "",
model: "",
brand: "",
}
};
what you are trying to achieve is cool, but instead of passing the variable name i,e the entire state object, you can just pass the key and you can assign that to the <input /> component.
const frameData = [
{
name: "frame-brand",
placeHolder: "Brand",
value: "brand",
},
{
name: "frame-year",
placeHolder: "Year",
value: "year",
},
{
name: "frame-model",
placeHolder: "Model",
value: "model",
},
];
and in the map function, you can use
return frameData.map((group) => {
return (
<div className="form-group">
<div className="col-sm-8">
<input
type="text"
className="form-control"
id={group.name}
name={group.name}
placeholder={group.placeHolder}
value={this.state.newSubmission.frame[group.value]}
onChange={(event) => this.updateValue(event)}
/>
</div>
</div>
);
});
Related
I have an array of objects as:
arr = [{
"name": "firstName",
"type": "text",
"required": false
},
{
"name": "lastName",
"type": "text",
"required": false
},
{
"name": "dob",
"type": "date",
"required": false
}
]
I am creating a form in HTML using *ngFor and formControlName as follows:
createFormgroup() {
arr.forEach((control: any) => {
this.createFormGroup.addControl(
control.name,
this.fb.control(false)
);
this.createFormGroup.addControl(
"required" + i,
this.fb.control(false)
);
});
}
submit(form: NgForm) {
console.log(form.value)
}
<mat-label>Enter form name:</mat-label>
<input class="form-name-input" matInput placeholder="Form Name" formControlName="formName" required>
<form [formGroup]="createFormGroup" (ngSubmit)="submit(createFormGroup)">
<div *ngFor="let a of arr; let i = index">
<mat-checkbox formControlName="{{a.name}}">{{ a.name }}</mat-checkbox>
<mat-checkbox formControlName="required{{i}}">required</mat-checkbox>
</div>
<button type="submit">submit</button>
</form>
Here I am trying to achieve to show and post the values together as an object respective to the other. For example, if I check the First name, dob, and the required in HTML, I want the whole object when clicked on submit().
When clicked on submit(), I am looking for output like:
form.value = [{
"name": "firstname",
"type": "text",
required: true
}, {
"name": "dob",
"type": "date",
required: false
}]
I am confused about how to achieve this. Is there a way? Also, I need to assign different formControl names for each required field. I am not sure how to do that.
I am attaching an image for understanding.
UI image
Note: I know how to get values from a form, but I am looking for a way to copy the whole object
To keep it simple: I need same array which I used to create form, and at the end I need same array with same object properties changed as per user input
You can achieve that like the following:
Define a sub-form group for each item in the source array, and add it to the main formGroup.
Add change event handler for the name checkboxes, to disable/enable the sub-form group, to be excluded from the value (In Angular, if the form-control or sub-form-group is disabled then its value will be excluded from the FormGroup.value)
Loop through the form array in the component template, and bind to the required form-control only.
So the component class, will look something like the following:
ngOnInit() {
this.formGroup = this.fb.group({
formName: null,
arrForm: this.fb.array(this.createSubFormGroup())
});
}
createSubFormGroup() {
return this.arr.map(
(control: { name: string; type: string; required: boolean }) => {
const subForm = this.fb.group({
name: control.name,
type: control.type,
required: control.required
});
// disable by default to be not included with the value until it's checked.
subForm.disable();
return subForm;
}
);
}
/** To enable/disable the sub formGroup if the checkbox checked/unchecked */
onNameCheckChange(ndx: number, event: MatCheckboxChange) {
(this.formGroup.get('arrForm') as FormArray).controls[ndx][event.checked ? 'enable' : 'disable']();
}
submit() {
console.log(this.formGroup.value?.arrForm || []);
}
And the component template, will look something like the following:
<form [formGroup]="formGroup" (ngSubmit)="submit()">
<mat-label>Enter form name:</mat-label>
<input class="form-name-input" matInput placeholder="Form Name" formControlName="formName" required>
<div formArrayName="arrForm">
<div *ngFor="let a of arr; let i = index" [formGroupName]="i">
<mat-checkbox (change)="onNameCheckChange(i, $event)">{{ a.name }}</mat-checkbox>
<mat-checkbox formControlName="required">required</mat-checkbox>
</div>
</div>
<button type="submit">submit</button>
</form>
And here is the working StackBiltz
1st Change :
this.createFormGroup.addControl(
"required" + i,
this.fb.control(false)
);
to
this.createFormGroup.addControl(
"required_" + control.name,
this.fb.control(false)
);
// your current result
const obj = {
'firstName': true,
'required_firstName': true,
'lastName': true,
'required_lastName': false,
'dob': true,
'required_dob': true
};
// your array
const arr = [
{
name: "firstName",
type: "text",
required: false
},
{
name: "lastName",
type: "text",
required: false
},
{
name: "dob",
type: "date",
required: false
}
];
const validKeys = Object.keys(obj).filter(key => obj[key] && (key.split("_").length==1));
// your required output
const finalResult = arr.filter(item => validKeys.includes(item.name)).map(item => ({...item, required: obj['required_'+item.name]}));
console.log(finalResult);
My component state has an array named concessions with 35 objects, here's the structure of one of those objects:
{
address:"Some street"
brands: [{
id: 1,
name: 'fiat'
}]
city:"Paris"
contact_name:""
email:""
id:1
latitude:"11.11111"
longitude:"22.22222"
name:"AGORA Cars"
opening_hours:"something"
phone:"969396973"
phone2:""
zipcode:"19100"
}
Now, I have a list rendered with all car brands and a checkbox for each one like this:
<div class="brands-filter col-10">
<span v-for="brand in brands" :key="brand.key" class="brand-card">
<div>
<input
type="checkbox"
:value="brand.name"
v-model="search_filters"
#click="filterConcessions()"
/>
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
Basically, for each clicked checkbox, I'm adding the brand to searched_filters and after that I want to filter the concessions array based on those filters.
In that click method, #click="filterConcessions()", I'm doing this:
filterConcessions: function () {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = [];
filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
this.concessions = filteredConcessions;
}
But, no matter what, it gives me an empty array.
Any advice?
It's because you need to use the #change event instead of #click.
Otherwise, search_filters isn't populated before filterConcessions is run:
new Vue({
el: "#app",
data: {
search_filters: [],
concessions: [{
address: "Some street",
brands: [{
id: 1,
name: 'fiat'
}],
city: "Paris",
contact_name: "",
email: "",
id: 1,
latitude: "11.11111",
longitude: "22.22222",
name: "AGORA Cars",
opening_hours: "something",
phone: "969396973",
phone2: "",
zipcode: "19100"
}]
},
methods: {
filterConcessions: function() {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
console.log(filteredConcessions)
this.concessions = filteredConcessions;
}
}
});
Vue.config.productionTip = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="brands-filter col-10" v-if="concessions[0]">
<span v-for="brand in concessions[0].brands" :key="brand.key" class="brand-card">
<div>
<input type="checkbox" :value="brand.name" v-model="search_filters" #change="filterConcessions()" />
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
</div>
After some search i figure how to solve this.
I've created a computed method:
computed: {
filteredConcessions() {
if (!this.search_filters.length) {
return this.concessions;
} else {
return this.concessions.filter((concession) =>
concession.brands.some((brand) =>
this.search_filters.includes(brand.name)
)
);
}
},
}
and at the for loop i iterate throw the "filteredConcessions":
<li v-for="concession in filteredConcessions" :key="concession.id" class="p-2">
And that solved my case!
I am trying to make a custom list filter on input using computed property. In one file I create a widget, in another I use it. Here is the code from the widget creation file:
<template>
<input value="Гарантийный случай"
v-model="searchText"
:class="{'w-autocomplete__input_completed': completed}"
ref="input"
#click="areOptionsVisible = !areOptionsVisible"/>
<div v-if="areOptionsVisible"
:style="{maxHeight: maxHeight, overflow: 'auto', zIndex: zIndex}"
class="w-autocomplete__items">
<div v-for="option in options" class="w-autocomplete__item_first" >
{{ option.name }}
<div v-for="item in option.children" class="w-autocomplete__item"
:class="{'w-autocomplete__item_active': currentIndex === item}"
#mouseenter="setActive(item)"
#keyup.up="changeCurrent('up', item)"
#keyup.down="changeCurrent('down', item)"
#click="doChoose(item)">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dropdown',
props: {
placeholder: {
type: String,
required: false,
default: '',
},
options: {
type: Array,
default(){
return []
}
},
},
data: function () {
return {
counter: 0,
currentIndex: null,
completed: false,
chosenItem: null,
areOptionsVisible: false,
searchText: '',
data: [],
}
},
computed: {
options(){
return this.props.options.filter(elem => {
return elem.name.toLowerCase().includes(this.searchText.toLowerCase());
});
},
},
.......
}
</script>
This is how I pass the array to this list in another file:
<template>
........
<div class="complaint-form__line-item">
<div class="form-group">
<label>Гарантийный случай</label>
<dropdown :options="options" />
</div>
</div>
........
</template>
<script>
........
export default {
name: 'complaint-form',
components: {LockedImport, UploadFiles, CarNumberInput, Autocomplete, Dropdown},
props: {
......
}
},
data() {
const complaint = new Complaint();
return {
........
options: [
{name: 'Выход детали из строя в процессе эксплуатации', value: null,
children: [{name: 'Увеличение зазора, люфт (дробь/стуки)', value: 53},
{name: 'Обрыв детали', value: 54}]},
{name: 'Поломка при установке', value: null},
{name: 'Брак до установки', value: null,
children: [{name: 'Недокомплект', value: 55},
{name: 'Заводской брак (замятия, отсутствие резьбы, пробой пыльника и т.д.)',
value: 56}]},
],
}
},
Tell me please why computed doesn't work? Only I add computed and the list is not displayed at all when clicking on the field, but should. That is, it breaks down completely. I want to be filtered when entering text in input
Vue.js cannot have more than one element inside a <template> tag, so I would suggest that you enclose the whole code of the dropdown component within a <div> tag or something of the sort.
Also, and this is just a comment, I would suggest that you use the focus event for the input because with click it will still be showing even if you aren't focusing on the input.
I'm creating an app where I need to store selected values in array nested in object (category below). State looks like:
state = {
data: {
user: "",
title: "",
text: "",
category: [], // should store values
},
updateNoteId: null,
}
In my render() I have following form:
<form onSubmit={this.submitNote}>
<Select
name="category"
value={this.state.data.category}
options={options}
onChange={this.handleMultiChange}
multi
/>
<input type="submit" value="Save" />
</form>
Options are:
const options = [
{ value: 1, label: 'one' },
{ value: 2, label: 'two' },
{ value: 3, label: 'three' }
]
So the question is how this.handleMultiChange function should looks like to work. Category[] need to keep all values selected in Select which is react-select component (eg. it should be category = [1,3], when 'one' and 'three' were chosen). I tried many combination but none of them worked so far. I prefer to use ES6 without any external libraries/helpers to do that.
handleMultiChange(selectedOptions) {
this.setState({
data: {
...this.state.data,
categories: selectedOptions
}
})
}
I made a component containing two dropdown lists. The options in the second dropdown menu is supposed to be filtered depending on the selected option from the first dropdown menu.
Now, I want to map a filtered array that is stored in a const similary to the way i map options1:
render() {
const options1 = [
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'}
];
const options2 = [
{value: 'one-a', label: 'One A', link: 'one'},
{value: 'one-b', label: 'One B', link: 'one'},
{value: 'two-a', label: 'Two A', link: 'two'},
{value: 'two-b', label: 'Two B', link: 'two'}
];
const filteredOptions = options2.filter(o => o.link === this.state.selectedOption.value);
return (
<div style={style}>
<div>
<label>Select one</label>
<select
value={this.state.selectedOption.value}
onChange={this.handleChange1}
>
{options1.map(tag => <option>{tag.value}</option>)}
</select>
</div>
<div>
<label>Then the other</label>
<select
value={this.state.selectedOption2.value}
onChange={this.handleChange2}
>
{filteredOptions.map(tag => <option>{tag.value}</option>)}
</select>
</div>
</div>
)
}
The first mapping of options1 works just fine. However, my select tag gets rendered empty for the mapping of filteredOptions.
I have no idea why it won't work. Anyone happen to have an idea?
Full code: https://www.codepile.net/pile/evNqergA
Here is a working example for what you're trying to do.
import React, { Component } from "react";
const options1 = [
{ value: "one", label: "One" },
{ value: "two", label: "Two" }
];
const options2 = [
{ value: "one-a", label: "One A", link: "one" },
{ value: "one-b", label: "One B", link: "one" },
{ value: "two-a", label: "Two A", link: "two" },
{ value: "two-b", label: "Two B", link: "two" }
];
export default class SelectsComponent extends Component {
handleChange1 = e => {
console.log(e);
this.setState({
selectedOption: { value: e.target.value }
});
};
handleChange2 = e => {
this.setState({
selectedOption2: { value: e.target.value }
});
};
constructor(props) {
super(props);
this.state = {
selectedOption: { value: "one" },
selectedOption2: { value: "one-a" }
};
}
render() {
const filteredOptions = options2.filter(
o => o.link === this.state.selectedOption.value
);
return (
<div>
<div>
<label>Select one</label>
<select
value={this.state.selectedOption.value}
onChange={this.handleChange1}
>
{options1.map(tag => (
<option>{tag.value}</option>
))}
</select>
</div>
<div>
<label>Then the other</label>
<select
value={this.state.selectedOption2.value}
onChange={this.handleChange2}
>
{filteredOptions.map(tag => (
<option>{tag.value}</option>
))}
</select>
</div>
</div>
);
}
}
In your scenario filteredOptions would be an empty Array.
The check for o.link === this.state.selectedOption.value is doing something wrong.
Check the value of this.state.selectedOption.value, this is not set correctly.
The best way to do this wouldn't be inside of the render method.
1) move your arrays into state or other instance members
2) make sure to only trigger the sorting once
this.setState(lastState => ({
...lastState,
options2: lastState.options2.filter(yourFilterFn)
}))
3) map the filtered array into JSX inside of your render method
Side-note: this uses immutable setState (which I gather is important given you create a new filtered array from the options2 in your example). If you want to follow an even more functional pattern, you can do the filtering inside of your render method (although I don't recommend it). If you decided to filter inside of your render method, consider using a memoization technique from React 16.7 (which is currently in Alpha).