I want my form to submit price data as an array format, currently, my form has a map, look like
{data.Type &&
<div>
{data.Type.map((datamapped)=>
<div key={datamapped._id}>
<p>{datamapped.TypeName}</p>
<Form.Item>
{getFieldDecorator(`price.${datamapped._id}.basePrice`)(
<Input placeholder="Base Price"/>,
)}
{getFieldDecorator(`price.${datamapped._id}.higherPrice`)(
<Input placeholder="Higher Price"/>,
)}
</div>
)}
</div>
}
Here I mapping my Type here and included, basePrice and higherPrice fields
result is :
price:
{
'5dc2913cf9e2372b11db4252': { basePrice: '0', higherPrice: '0' },
'5dc2a109f9e2372b11db4253': { basePrice: '0', higherPrice: '0' }
},
I want the above result as an array format how to do it?
Another way to get values as an array is to provide an array as FormItem name attribute like this:
<FormItem name={['wrapperName', index, 'propertyName']}>
<Input/>,
</FormItem>
Try change datamapped._id to [index]
{data.Type &&
<div>
{data.Type.map((datamapped, index)=>
<div key={datamapped._id}>
<p>{datamapped.TypeName}</p>
<Form.Item>
{getFieldDecorator(`price[${index}].basePrice`)(
<Input placeholder="Base Price"/>,
)}
{getFieldDecorator(`price[${index}].higherPrice`)(
<Input placeholder="Higher Price"/>,
)}
</div>
)}
</div>
}
You can try this:
var json = {
price: {
"5dc2913cf9e2372b11db4252": { basePrice: "0", higherPrice: "0" },
"5dc2a109f9e2372b11db4253": { basePrice: "0", higherPrice: "0" }
}
};
json.price = Object.entries(json.price);
console.log(json);
📒my Dynamic Form's note
antd#3.x
const MyForm = ({ form = {} }) => {
const { getFieldValue, getFieldDecorator } = form;
const [defaultData, setDefaultData] = useState({});
const add = ()=>{
// 🚩
defaultData.price.push({basePrice: 'zz',
higherPrice: 'hzz',key: new Date().getTime()})
}
return (
<Form>
<Form.Item>
{Array.isArray(defaultData.price) && // 🚩
defaultData.price.map((datamapped, index) => {
return (
<div key={datamapped.id||datamapped.key}>
{getFieldDecorator(`price[${index}].basePrice`, { // 🚩
initialValue: datamapped.basePrice,
})(<Input placeholder="Base Price" />)}
{getFieldDecorator(`price[${index}].higherPrice`, {
initialValue: datamapped.higherPrice,
})(<Input placeholder="Higher Price" />)}
</div>
);
})}
</Form.Item>
<Button onClick={add}> + </Button>
</Form>
);
};
export default Form.create()(MyForm);
data structure
|-- price array[object]
|-- basePrice string
|-- higherPrice string
🚩must use useState to assist setFieldsValue
const [defaultData, setDefaultData] = useState({})
setDefaultData({
price: [
{
basePrice: 'xx',
higherPrice: 'hxx',
},
{
basePrice: 'yy',
higherPrice: 'hyy',
},
],
});
⚠️ setFieldsValue only works in the first calling
Related
I have react select using sortable container the problem I am having is that the values that are extracted is like this
{
"fruits": [
{
"fruitName": {
"id": 3,
"value": "vanilla",
"label": "Vanilla"
}
},
{
"fruitName": {
"id": 1,
"value": "chocolate",
"label": "Chocolate"
}
}
]
}
if you notice that fruitName is duplicated each time I select an option despite that I don't need it I just want it like a list like this
{
"fruits": [
{
"id": 3,
"value": "vanilla",
"label": "Vanilla"
},
{
"id": 1,
"value": "chocolate",
"label": "Chocolate"
}
]
}
and if I remove fruitName field from field name it doesn't work correctly also how to pass initial values to this if I already have selected list values of fruits
import React from "react";
import Styles from "./Styles";
// import { render } from "react-dom";
import { Form, Field } from "react-final-form";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {
SortableContainer,
SortableElement,
SortableHandle,
} from "react-sortable-hoc";
import Select from "react-select";
const options = [
{ id: 1, value: "chocolate", label: "Chocolate" },
{ id:2, value: "strawberry", label: "Strawberry" },
{ id:3, value: "vanilla", label: "Vanilla" },
];
const DragHandle = SortableHandle(() => (
<span style={{ cursor: "move" }}>Drag</span>
));
const SortableItem = SortableElement(({ name, fields, value }) => (
<li>
<DragHandle />
<Field name={`${name}.fruittName`}>
{({ input }) => (
<Select options={options} placeholder="Select Location" {...input} />
)}
</Field>
<span onClick={() => fields.remove(value)} style={{ cursor: "pointer" }}>
Remove
</span>
</li>
));
const SortableList = SortableContainer(({ items }) => {
return (
<ul>
{items.map((name, index) => (
<SortableItem
key={`item-${index}`}
index={index}
value={index}
name={name}
fields={items}
/>
))}
</ul>
);
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const onSubmit = async (values) => {
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
};
const sortEnd =
(move) =>
({ oldIndex, newIndex }) => {
move(oldIndex, newIndex);
};
const Home = () => {
return (
<div>
<Styles>
<h1>React Final Form - Array Fields</h1>
<a href="https://github.com/erikras/react-final-form#-react-final-form">
Read Docs
</a>
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators,
}}
render={({
handleSubmit,
form: {
mutators: { push, pop },
},
pristine,
reset,
submitting,
values,
}) => {
return (
<form onSubmit={handleSubmit}>
<div>
<label>Company</label>
<Field name="company" component="input" />
</div>
<div className="buttons">
<button
type="button"
onClick={() => push("fruits", undefined)}
>
Add Customer
</button>
<button type="button" onClick={() => pop("fruits")}>
Remove Customer
</button>
</div>
<FieldArray name="fruits">
{({ fields }) => (
<SortableList
useDragHandle={true}
items={fields}
onSortEnd={sortEnd(fields.move)}
/>
)}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Reset
</button>
</div>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
);
}}
/>
</Styles>
</div>
);
};
export default Home;
This will help your answer,
If you don't want to repeat an object/value you have to just filter out it.
Below is an example,
fruits = [
{id: 1, name:'Mango'},
{id: 2, name:'Apple'},
{id: 3, name:'Orange'}
]
If your selected id is 2, So you want every other value except 2,
let selectedId = 2;
let filteredFruitArray = fruits.filter((fruit) => fruit.id !== selectedId);
console.log(filteredFruitArray); //{id: 1, name:'Mango'}{id: 3, name:'Orange'}
I've created script for dynamic form, but there's 2 things which I can't get and my head is exploding right now, hopefully somebody would help me with that.
After creating new fields - I can't remove fields depends on button which was clicked.
And after removing some of those fields, I have this error with fieldsenter image description here
import React from "react";
import {useState , useEffect} from "react";
import ReactDOM from "react-dom";
import "./index.css";
const Form = () =>{
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([{
id : 1,
name: "",
phone: "",
age: ""
}])
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{
fields.map((elem, index) => {
return(
<FormElement {...elem} fields={fields} fieldsChanger={fieldsChanger} fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger}/>
)
})
}
<AddMore fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger} fields={fields} fieldsChanger={fieldsChanger}/>
</div>
</>
)
}
const FormElement = ({fieldsLength ,...props}) =>{
function inputHandler(e, id){
console.log(e.target.name);
const values = [...props.fields];
values[id-1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e,id){
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function(elem,index){
console.log("index:" , index, "ID : ", id);
if (index + 1 != id){
return elem;
}
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([])
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input type="text" value={props.fields.name} name="name" onChange={e => inputHandler(e , props.id)} />
</div>
<div className="form__element">
<input type="text" value={props.fields.phone} name="phone" onChange={e => inputHandler(e , props.id)}/>
</div>
<div className="form__element">
<input type="text" value={props.fields.age} name="age" onChange={e => inputHandler(e , props.id)}/>
</div>
{
fieldsLength > 1 ? <div className="remove__field">
<a href="#" onClick={e=>removeElement(e , props.id)}>Remove</a>
</div> : ""
}
</div>
)
}
const AddMore = (props) =>{
function addMore(){
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([...props.fields, {id:props.fields.length + 1 , name: "" , phone : "" , age :''} ]);
}
return (
<div className="add__more">
<a href="#" onClick={e=> addMore()}>Add element</a>
</div>
)
}
ReactDOM.render(<Form/> , document.getElementById("root"));
Where I'm wrong - would be really helpfull to understand what is the problem
Always use key when rendering list.
<FormElement key={elem.id} {...elem} ...
https://reactjs.org/docs/lists-and-keys.html#keys
1. You should use the props.id to remove a form instead of using index.
e.g. this must be a bug.
if (index + 1 != id) {
return elem;
}
2. And you should use id by the unique value generation instead of using array's length.
3. You should provide the key prop in the render of the form fields. Otherwise, React can't distinguish forms in rendering.
And the key should be unique whenever you add or remove items.
e.g. See the updated code. You need to use elem.id. (Of course, you should generate id in the add method.
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
4. You could basically use a timestamp. Please use your own generation logic in the production. You can use uuid generation package.
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
My advices:
You don't need to use this state variable const [fieldsLength, fieldsLengthChanger] = useState(1);
Because we can get this value by fields.length.
There are a few problems in terms of components design. e.g. Please try to define addMore() in form then you don't need to pass fields, fieldsChanger as props.
Here is a full working code with your old code commented.
const Form = () => {
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
]);
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
<AddMore
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
fields={fields}
fieldsChanger={fieldsChanger}
/>
</div>
</>
);
};
const FormElement = ({ fieldsLength, ...props }) => {
function inputHandler(e, id) {
console.log(e.target.name);
const values = [...props.fields];
const item = values.find((x) => x.id === props.id);
if (item) {
item[e.target.name] = e.target.value;
}
// values[id - 1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e, id) {
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function (elem, index) {
/*console.log("index:", index, "ID : ", id);
if (index + 1 != id) {
return elem;
}*/
return elem.id !== id;
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([]);
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input
type="text"
value={props.fields.name}
name="name"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.phone}
name="phone"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.age}
name="age"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
{fieldsLength > 1 ? (
<div className="remove__field">
<a href="#" onClick={(e) => removeElement(e, props.id)}>
Remove
</a>
</div>
) : (
""
)}
</div>
);
};
const AddMore = (props) => {
function addMore() {
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([
...props.fields,
//{ id: props.fields.length + 1, name: "", phone: "", age: "" }
{ id: new Date().getTime(), name: "", phone: "", age: "" }
]);
}
return (
<div className="add__more">
<a href="#" onClick={(e) => addMore()}>
Add element
</a>
</div>
);
};
I implemented the form through react final form
const products= [
{ label: "T Shirt", value: "tshirt" },
{ label: "White Mug", value: "cup" },
{ label: "G-Shock", value: "watch" },
{ label: "Hawaiian Shorts", value: "shorts" },
];
<>
<Form
onSubmit={onSubmit}
render={({ handleSubmit, pristine, invalid, values }) => (
<form onSubmit={handleSubmit} className="p-5">
{products &&
products.map((product, idx) => (
<div className="custom-control custom-checkbox" key={idx}>
<Field
name="state"
component="input"
type="checkbox"
value={product.value}
/>
<label
className="custom-control-label"
htmlFor={`customCheck1-${product.value}`}
>
{product.label}
</label>
</div>
))}
<button type="submit" disabled={pristine || invalid}>
Submit
</button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
If I am selecting checkboxes the checked values are showing array of values like [tshirt,cup] but I need to show the array of objects like [ { label: "T Shirt", value: "tshirt" }, { label: "White Mug", value: "cup" }]
I tried so many ways but I have not any luck. Please help me to out of this problem
values will always be the array consisting of the "value" attribute for the Field tag.
If you want the object from the products array,you could do the following
console.log(values.map(val => products.find(p => p.value === val)))
or create an object first via reduce & then use it.
const obj =products.reduce((map,p)=>{
map[value]=p
return map
},{})
console.log(values.map(v => productMap[v]))
add a onchange method to you input. the method must take value of product.
const [selectedProducts, setSelectedProducts] = useState([]);
const handleChange = (value) =>{
const itemToAdd = products.find(product => product.value === value);
const index = selectedProducts.findIndex(item => item.value === value);
if (index === -1){
setSelectedProducts([...selectedProducts, products[index]])
}else {
const data = [...selectedProducts];
data.splice(index, 1);
setSelectedProducts(data);
}
}
some change to jsx
<Field
onChange = {handleChange}
name="state"
component="input"
type="checkbox"
value={product.value}
checked = {selectedProducts.findIndex(item => item.value === value)!== -1}
/>
i'm trying to implement array of objects of a structure like this
selectedItems: [
{
_id: ""
}
]
what I want to do is when the user selects for example 2 or more _id, I want the structure to be like this
[
{
_id: "123"
},
{
_id: "456"
},
{
_id: "789"
},
]
but what I currently get with my implementation is an array of _id that will contain several items like this
[
{
_id: ["123", "456", "789"]
}
]
I followed the documentation of formik which suggests to implement this solution when we have an array of objects.
my implementation
const GetSelectedItems = () => {
return (
<Formik
initialValues={{
selectedItems: [{
_id: ""
}]
}}
onSubmit={values => {
console.log(values)
}}
render={({values, handleChange}) => (
<Form>
<FieldArray
name="selectedItems"
render={arrayHelpers => (
<div>
{values.selectedItems && values.selectedItems.length > 0 ? (
values.selectedItems.map((item, index) => (
<div key={index}>
<Field as="div"
name={`selectedItems${[0]}._id`}
>
<select name={`selectedItems.${[0]}._id`}
multiple={true}
className="form-control"
value={item._id}
onChange={event => {
handleChange(event);
}}
>
<option value={values.selectedItems._id}>
Choisir items
</option>
{itemList(items)} // data from api
</select>
</Field>
</div>
))
) : null}
<div>
<div>
<button type="submit">Submit</button>
</div>
</div>
</div>
)}
/>
</Form>
)}
/>)
}
You don't need to give a name prop to select's option components, just remove it and your code will run as expected:
// Removed name props from this component
<option key={option._id} value={`${option._id}`}>
{option._id}
</option>
i am tying to build a multi select of items. my backend data structure is an array of objects like this
{
"selectedItems": [
{"_id" : ""}
]
}
the problem with react-select is when i select one or many items, the structure does not match with my backend route, it displays like this
{
"selectedItems": [
{"value" : "", label : ""}
]
}
i am working with Formik to manage the form and you can also see the result on this sandbox on console log
const ItemSelected = () => {
const items = [
{
_id : "123", name : "john", desc : 'eb'
},
{
_id : "456", name : "doe", desc : 'ec'
},
{
_id : "789", name : "seal", desc : 'ef'
}
]
const itemList = (options) => {
return (
options &&
options.map(option => {
return {
value: option._id,
label: option.name
};
})
);
}
return(
<div>
<Formik
initialValues={{
selectedItems : []
}}
onSubmit={values => {
console.log(values)
}}
>
{({
values,
handleSubmit,
setFieldValue
}) => (
<Form onSubmit={handleSubmit}>
<div className="row">
<div className="col">
<Select
isMulti
name={`selectedItems`}
value={values.selectedItems}
onChange={e=>setFieldValue(`selectedItems`, e)}
options={itemList(items)}
className="basic-multi-select"
classNamePrefix="select"
/>
</div>
<div className="col">
<button type="submit">
submit
</button>
</div>
</div>
</Form>
)}
</Formik>
</div>
)
}
You need to use map to create the data structure you want when you handle submit.
onSubmit={values => {
if(values.selectedItems){
const data = values.selectedItems.map(value => ({_id: value.value}))
console.log(data);
}
}}