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);
}
}}
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 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 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
I am using react-select and redux form for autocomplete. There is a field called listings_of which is an array of data i need to post to the server. For this I used FieldArray concept. The data i get is not in value and label format which react-select expects so I have to use getOptionLabel and getOptionValue props. For this i did the following way
const renderListingsOf = ({
fields,
data,
meta: { touched, error, submitFailed }
}) => {
return (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>
Add Member
</button>
</li>
{fields.map((listing, index) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<Field
name={`${listing}`}
component={InputTextWithSearch}
placeholder="Search..."
options={(data && data.users.data) || []}
getOptionLabel={option =>
!isEmptyObject(option) && option.personal.first_name
}
getOptionValue={option => {
if(!isEmptyObject(option)) {
return {
_id : option._id,
first_name: option.personal.first_name,
last_name : option.personal.last_name
};
}
}}
/>
</li>
))}
</ul>
);
};
<Column>
<Query
query={GET_USERS}
variables={{
param: {
limit: 10
}
}}
>
{({ loading, data }) => {
if(loading)return 'loading...';
return (
<InputFieldWrapper styling={styling} label="Listings of">
<FieldArray
name="general.general_information.listings_of"
component={renderListingsOf}
data={data}
/>
</InputFieldWrapper>
);
}}
</Query>
</Column>
data.users.data has the following object
[
{
_id: 'sdfskfjsdkjfsd',
personal: {
first_name: 'hello',
last_name: 'hy'
}
},
{
_id: 'sdjfkjfdkjskfdjkfff',
personal: {
first_name: 'hello1',
last_name: 'hy1'
}
},
]
but when submitting the value for general.general_information.listings_of should be an array of object as below
general: {
general_information: {
listings_of: [
{
_id: 'sdfskfjsdkjfsd',
first_name: 'hello',
last_name: 'hy'
}
]
}
}
thus i tried to return the following object from getOptionValue but its not working its like this
general: {
general_information: {
listings_of: [
{
_id: 'sdfskfjsdkjfsd',
personal: {
first_name: 'hello',
last_name: 'hy'
}
}
]
}
}