select upto 3 in category list from array in list in react.js - javascript

I am working in react, fixed-sized array but I am not getting output when I am select up to 3 record but it show only 1 record select.
CODE:
this.state = {
type: [3], //select upto 3 record(type select from category)
categoryList: null, // category-list
};
changeCategory(o){
this.setState({type: o})
}
<div>
{ categoryList.map((o,index)=>{
return <div key={index} className={"rounded " + (type==o.slug ?"selected" : '')} onClick={()=>this.changeCategory(o.slug)} style={{padding: "2px 5px"}}>{o.name}</div>
})
}
</div>

ISSUE
type is a state array and you are comparing it like the value in the render method. In this way, you always have just one item selected instead of 3 items.
SOLUTION
This code will help you to select 3 items
this.state = {
type: [],
....
};
// this will add selected item in `type` array if length not exceed
changeCategory(o){
if(type.length !== 3){ // for limit of 3
const newType = [...this.state.type];
newType.push(o.slug);
this.setState({type: newType});
}
}
<div>
{categoryList.map((o, index) => {
return (
<div
key={index}
className={
"rounded " + (this.state.type.includes(o.slug) ? "selected" : "")
}
onClick={() => this.changeCategory(o.slug)}
style={{ padding: "2px 5px" }}
>
{o.name}
</div>
);
})}
</div>

Related

Javascript splice array method is not working as expected

,
I was working out with splice methods of js , but as it may seem it was not working exactly as it should remove any element from an array .
Currently its only deleting the last element from array even after providing the index value for it to delete from the array. in console.log i get the perfect output after deleting anything , but in UI part it does not update as it should , its only removing the last element from array even if i click on delete other item . How can i resolve this ?
Here's what i've tried so far :
const add_actions_options = [
{value : "Postback" , label:intl.formatMessage({ id: 'POSTBACK' })},
{value : "Uri" , label:intl.formatMessage({ id: 'URI' })}
]
const [ actions , setActions ] = useState<any | undefined>([{type : add_actions_options[0].value , label : "" , data : ""}])
const [selectOptions, setSelectOptions] = useState<any>(add_actions_options);
function addAction(){
if(actions.length < 4 ){
setSelectOptions([...add_actions_options])
setActions([...actions , {type : selectOptions[0].value , label : "" , data : ""}])
} else {
toast(intl.formatMessage({ id: 'MAX.ALLOWED.4' }), { type: "error" })
}
}
function deleteAction(index){
if(actions.length === 1 ){
toast(intl.formatMessage({ id: 'MIN.ALLOWED.1' }), { type: "error" })
} else {
const updatedFields = [...actions];
updatedFields.splice(index, 1);
console.log('index : ' , index)
console.log('updatedFields : ' , updatedFields)
setActions(updatedFields);
}
}
<div className='row my-6'>
<div className='col-lg-3 py-2'>
<h4><label className="form-label">{intl.formatMessage({ id: 'ACTIONS' })}*</label></h4>
<button className='btn btn-primary btn-sm btn-block' onClick={() => addAction()}>
<KTSVG path='/media/icons/duotune/arrows/arr075.svg' className='svg-icon-2' />
{intl.formatMessage({id: 'ADD.ACTION'})}
</button>
</div>
</div>
<div className='row my-6 '>
{ actions.map((item , index) => {
return(
<div key={index} className='row my-6'>
<div className='col-lg-4 py-2'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.TYPE' })}*</label></h4>
<Select
onChange={(value) => handleTypeChange(index, value)}
options={selectOptions}
/>
</div>
<div className='col-lg-3 py-2'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.LABEL' })}*</label></h4>
<input
{...formik_buttons_type.getFieldProps('action.label')}
className="form-control form-control-lg form-control-solid"
name='action.label'
id='action_label'
type="text"
maxLength={30}
onChange={(event) => handleLabelChange(index, event.target.value)}
value={actions.label}
required
onInvalid={(e) => checkLabelValidation(e)}
onInput={(e) => checkLabelValidation(e)}
/>
</div>
<div className='col-lg-3 py-2'>
<h4><label className="form-label">{intl.formatMessage({ id: 'TEMPLATE.DATA' })}*</label></h4>
<input
{...formik_buttons_type.getFieldProps('action.data')}
className="form-control form-control-lg form-control-solid"
name='action.data'
id='action_data'
type="text"
maxLength={100}
onChange={(event) => { handleDataChange(index, event.target.value); }}
value={actions.data}
required
onInvalid={(e) => checkDataValidation(e)}
onInput={(e) => checkDataValidation(e)}
/>
</div>
<div className='col-lg-2 py-2 mt-10'>
<OverlayTrigger
delay={{ hide: 50, show: 50 }}
overlay={(props) => (
<Tooltip {...props}>
{intl.formatMessage({ id: 'DEL.ACTION' })}
</Tooltip>
)}
placement="top">
<button
type='button'
style={{display: index === 0 ? 'none': 'inline-block'}}
className='btn btn-icon btn-md btn-bg-light btn-color-danger me-1'
onClick={() => deleteAction(index)}
>
<i className='fa fa-trash'></i>
</button>
</OverlayTrigger>
</div>
</div>
)
})}
</div>
I am able to receive exact index number perfect output from the logs below in deleteAction fields , but the view in browser deletes the last column(index) from the array of actions. :
console.log('index : ' , index)
console.log('updatedFields : ' , updatedFields)
can anyone help me with this ?
code sand box : https://codesandbox.io/s/vibrant-christian-bktnot
Thanks and Regards !
Whenever using the index as a key for an element. We have to ensure we are not modifying the state array to avoid bugs. If you are modifying as #Dave suggested use unique keys.
The problem here is using the index as key, When we remove an element from an array react compares the previous keys [0,1,2,3] with new keys [0,1,2].
If you notice closely, Even if we remove index (1) using splice(1,1) method. The elements which are rendered again have starting index of 0.
React compares keys previous keys [0,1,2,3] with new keys [0,1,2] and finds out that index 3 is removed hence it every time removes the 3rd element in the above example (or the last index) from DOM. However, your state is reflecting the correct array element.
To avoid this use a unique key.
Codesandbox for a working example.
If you are not having keys in objects, To generate unique keys we can use one of the following as per your use case:
crypto.randomUUID();
Date.now().toString(36) + Math.random().toString(36).substr(2)
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
When you do
{ actions.map((item , index) => {
return(
<div key={index}
you are pretty much asking for issues with component state. When react is trying to determine which element in the UI to update, and it sees "the array with a length of 6 now has a length of 5, which element do I need to remove", it will find the one with a key that no longer exists, in this case the one at the end since you used index as key, and will remove it.
I would probably do
function randomId(){
const uint32 = window.crypto.getRandomValues(new Uint32Array(1))[0];
return uint32.toString(16);
}
const [ actions , setActions ] = useState<any | undefined>([{id: randomId(), type : add_actions_options[0].value , label : "" , data : ""}])
function addAction(){
if(actions.length < 4 ){
setSelectOptions([...add_actions_options])
setActions([...actions , {id: randomId(), type : selectOptions[0].value , label : "" , data : ""}])
} else {
toast(intl.formatMessage({ id: 'MAX.ALLOWED.4' }), { type: "error" })
}
}
{ actions.map((item) => {
return(
<div key={item.id}
where randomId can be anything that gives you a unique ID, or (even better) if there is an existing property on the actual data that uniquely identifies it, you could use that.

How to compare objects of two different arrays in javascript

I have a react application and in that I have the following list of objects.
const orderItems = [
{
id: 1,
name: "item 1",
price: "100",
availableQty: 2,
out: ""
},
{
id: 2,
name: "item 2",
price: "100",
availableQty: 2,
out: ""
},
{
id: 3,
name: "item 3",
price: "100",
availableQty: 2,
out: ""
},
{
id: 4,
name: "item 4",
price: "100",
availableQty: 2,
out: ""
}
];
Im mapping through the orderItems list and display the name with a checkbox. I have a state named selectedItem and when the checkbox is checked, new object adds to the selectedItem along with the id of the orderItems as shown in the following code.
const handleSelect = (e, item) => {
const { checked } = e.target;
const newObj = {
id: item.id,
isAdded: true,
reason: "",
issue: "",
availableStock: ""
};
if (checked) {
setselectedItem((previousState) => [...previousState, newObj]);
}
if (checked === false) {
const filteredItems = selectedItem.filter(
(singleItem) => singleItem.id !== item.id
);
setselectedItem(filteredItems);
}
};
In the react app, Im mapping the orderItems and showing the name with a checkbox. When the checkbox is checked, I add a new object to selectedItem with the id of orderItems and if the selectedItem is empty, I display a as "list is empty". If checkbox is selected, I display a input tag under the name and if its not selected, i display a label with the text "not selected" as shown in the code below.
<div className="app">
{items.map((item) => (
<div className="issue">
<input type="checkbox" onChange={(e) => handleSelect(e, item)} />
<label>{item.name}</label>
{selectedItem.length > 0 ? (
<>
{selectedItem.map((singleItem) =>
singleItem.id === item.id ? (
<input type="number" />
) : (
<label>not selected</label>
)
)}
</>
) : (
"list is empty"
)}
</div>
))}
</div>
The issue here is, when one item is checked, it show input tag under selected item and shows "not selected" label under other items but if i select two checkboxes, both not selected label and input tag displays. If i select 3 checkboxes, not selected label displays two times with input tag.
What i want is to display only the input tags under selected items and display not selected label under unchecked items.
codesandbox link: https://codesandbox.io/s/minsaf-2jjt2g
You are mapping the selectedItem array for every element in the item array. This means for any element in the array that some condition matches and both the input and label are rendered.
If I understand the code and your expected behavior I think you should conditionally render a single input or label based on if the selectedItem array includes an element matching by id.
Example:
<div className="app">
{items.map((item) => (
<div className="issue">
<input type="checkbox" onChange={(e) => handleSelect(e, item)} />
<label>{item.name}</label>
{selectedItem.length ? (
selectedItem.some((singleItem) => singleItem.id === item.id) ? (
<input type="number" />
) : (
<label>not selected</label>
)
) : (
"list is empty"
)}
</div>
))}
</div>

getting error Dropdown `value` must be an array when `multiple` is set. Received type: `[object String]`

I am getting this error while using multiselect with final-form.
Dropdown value must be an array when multiple is set. Received
type: [object String]
here is my code:
https://codesandbox.io/s/cool-torvalds-lhe9d
<Dropdown
{...props.input}
clearable
fluid
multiple
search
onChange={(e, data) => {
return data.value.length > 0
? input.onChange(data.value)
: input.onChange("");
}}
onSearchChange={onSearchChange}
selection
defaultValue={[]}
options={data}
placeholder="Select values"
/>
any update?
You need to remove defaultValue prop and pass value prop as [] if value is not available to Dropdown component.
const SingleSelectAutoComplete = props => {
const renderError = ({ error, touched }, id) => {
if (touched && error) {
return <div id={id}>{error}</div>;
}
};
const {
input,
label,
onSearchChange,
data,
meta,
required,
onChange,
helloWorld
} = props;
console.log("***********************");
let { value, ...restProps } = props.input;
const id = input.name;
return (
<div
className={`field ${meta.error && meta.touched ? " error" : ""} ${
required ? " required" : ""
}`}
>
<label>{label}</label>
<Dropdown
{...restProps}
value={value || []}
clearable
fluid
multiple
search
onChange={(e, data) => {
return data.value.length > 0
? input.onChange(data.value)
: input.onChange("");
}}
onSearchChange={onSearchChange}
selection
options={data}
placeholder="Select values"
/>
{renderError(meta, `${id}-error-text`)}
</div>
);
};

How to remove unchecked items from cart state in React

I have items displayed with check boxes and once clicked/checked they are added to the cart. When unchecked they are added to the cart again. I am not sure how to toggle the checked to remove the item when unchecked.
export class DestinationPrices extends React.Component {
constructor(props) {
super(props);
this.state = {
flightData: [],
isChecked: props.isChecked || false,
data: "",
cart: []
};
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
// ...
}
Select(FD) {
return (
<div>
{FD.FlightID}
<label>
<Checkbox
id={FD.FlightID}
name={FD.FlightID}
value={this.state.isChecked}
onChange={() => this.handleCheckboxChange(FD)}
/>
<span>Select</span>
</label>
</div>
);
}
handleCheckboxChange = id => {
const cartItem = this.state.cart.filter(x => x.FlightID === id.FlightID);
this.setState({
isChecked: !this.state.isChecked,
cart: [...this.state.cart, id]
});
}
When the item is checked, it displays in the cart with some details.
With every click of the checkbox, the item adds to state and shows in the card, regardless if it is checked or unchecked. I need to delete the item from state if the item is unchecked.
Do I check if the item is already there and delete from cart?
render() {
var data = this.props.cart;
var arry = [];
arry.push(data);
return (
<div>
<Card body>
<CardTitle>
<h3>
<b>Flights Selected : </b>
</h3>
</CardTitle>
<CardText>
<span className="fa fa-cart-plus fa-2x"> {data.length} </span>{" "}
{this.getData()}
</CardText>
<div />
</Card>
</div>
);
}
Any help would be greatly appreciated, thanks!
One way of going about it is to do how you outlined in your question: check if the item already is in the cart. If it is, you can remove it from the cart. It it's not, you can add it to the cart.
handleCheckboxChange = item => {
this.setState(prevState => {
const isItemInCart = prevState.cart.some(el => el.FlightID === item.FlightID);
const cart = isItemInCart
? prevState.cart.filter(el => el.FlightID !== item.FlightID)
: [...prevState.cart, item];
return { cart };
});
}

Mapping Nested Checkbox not working ReactJS

I have a function which triggers children checkboxes once main checkbox is checked, and all these checkboxes are mapped from JSON. The main checkboxes (Highest order) and all of its children checkboxes (2nd order) under them are shown on check and its working great, what i am trying to show is the children of those children of the main checkboxes (3rd order).
Basically to show all three orders under each other on check, and add the 3rd order to my current code, so Options Group shows Options, and under Options is what i want to show, which are Option 1, Option 2, option 3 and so on..
The checkbox values are passed as props from Checkbox.js to Itemlist.js where the fetch/map happens.
As I was trying to achieve this, I have been able to show each of these 3rd order checkboxes but each one alone instead of showing them as nested checkboxes. I've tried to include it in the existing mapping function to show them all as nested checkboxes as mentioned but it couldn't work it only works if it was mapped alone instead of the current mapped path which is const selectedItem. while the mapping path of the targeted checkboxes (3rd level) are added in Itemlist.js as const selectedMod,
Main Snippet : https://codesandbox.io/embed/6jykwp3x6n?fontsize=14
What I reached for so far to show the targeted checkboxes but individually: https://codesandbox.io/embed/o932z4yr6y?fontsize=14
Checkbox.js
import React from "react";
import "./Checkbox.css";
class Checkboxes extends React.Component {
constructor(props) {
super(props);
this.state = {
currentData: 0,
limit: 2,
checked: false
};
}
selectData(id, event) {
let isSelected = event.currentTarget.checked;
if (isSelected) {
if (this.state.currentData < this.props.max) {
this.setState({ currentData: this.state.currentData + 1 });
} else {
event.preventDefault();
event.currentTarget.checked = false;
}
} else {
if (this.state.currentData >= this.props.min) {
this.setState({ currentData: this.state.currentData - 1 });
} else {
event.preventDefault();
event.currentTarget.checked = true;
}
}
}
render() {
const input2Checkboxes =
this.props.options &&
this.props.options.map(item => {
return (
<div className="inputGroup2">
{" "}
<div className="inputGroup">
<input
id={this.props.childk + (item.name || item.description)}
name="checkbox"
type="checkbox"
onChange={this.selectData.bind(
this,
this.props.childk + (item.name || item.description)
)}
/>
<label
htmlFor={this.props.childk + (item.name || item.description)}
>
{item.name || item.description}{" "}
</label>
</div>
</div>
);
});
return (
<form className="form">
<div>
{/** <h2>{this.props.title}</h2>*/}
<div className="inputGroup">
<input
id={this.props.childk + this.props.name}
name="checkbox"
type="checkbox"
checked={this.state.checked}
onChange={this.selectData.bind(
this,
this.props.childk + this.props.uniq
)}
onChange={() => {
this.setState({
checked: !this.state.checked,
currentData: 0
});
}}
/>
<label htmlFor={this.props.childk + this.props.name}>
{this.props.name}{" "}
</label>
</div>{" "}
{this.state.checked ? input2Checkboxes : undefined}
</div>
</form>
);
}
}
export default Checkboxes;
Itemlist.js Where the mapping function happen
...
const selectedItem =
selectedChild.children && selectedChild.children.length
? selectedChild.children[this.state.itemSelected]
: null;
...
<div>
{selectedItem &&
selectedItem.children &&
selectedItem.children.map((item, index) => (
<Checkboxes
key={index}
name={item.name || item.description}
myKey={index}
options={item.children}
childk={item.id}
max={item.max}
min={item.min}
/>
))}
</div>
...

Categories

Resources