I have an application to divide between available and non-available coupon, when user clicked available coupon the coupon will go to non-available section (vice versa), everything works fine. But, if I searched the coupon in the search box, the bug happens, turns out I can't get the initial index of the searched coupon (it will always change to index 0)
For instance, Coupon B should have index 1, but when i type Coupon B in the search box the index will change to 0, which break my changedHandler logic. How can i solve this?
You can see my application in Action through this link https://go-coupon-d0fe4.firebaseapp.com/
MainContainer.Jsx
state = {
availableCoupon: ['Coupon A', 'Coupon B', 'Coupon C', 'Coupon D'],
nonAvalailable: ['filter0', 'filter1', 'filter2', 'filter3'],
text: ''
};
searchHandler = text => {
this.setState({ text });
};
changedHandler = i => {
console.log(i);
const r = window.confirm('Use Coupon?');
if (r) {
let newItems = [...this.state.nonAvalailable];
let updatedItems = this.state.availableCoupon.splice(i, 1);
newItems.push(...updatedItems);
this.setState({
nonAvalailable: newItems
});
console.log('THIS IS NEW ITEMS ' + updatedItems);
}
return false;
};
render(){
return(
<SearchBox
searchTerm={this.state.text}
filteredList={this.state.availableCoupon}
searchHandler={this.searchHandler}
/>
<FilteredItem
changed={this.changedHandler}
searchTerm={this.state.text}
availableCoupon={this.state.availableCoupon}
/>
)
}
SearchBox.jsx
<Input
onChange={e => this.props.searchHandler(e.target.value)}
value={this.props.text}
type="text"
name="coupone"
id="exampleCoupon"
placeholder="Coupon Goes Here"
/>
filteredItem.jsx
{this.props.availableCoupon
.filter(
item =>
`${item}`
.toUpperCase()
.indexOf(this.props.searchTerm.toUpperCase()) >= 0
)
.map((item, index) => {
return (
<Col key={index} className="mt-3" md="2" lg="2" sm="3" xs="4">
<Card onClick={() => this.props.changed(index)}>
<CardBody>
<CardTitle>{item}</CardTitle>
</CardBody>
</Card>
</Col>
);
})}
So, the reason it is not working is because the key attribute that you rely on changes every time you actually search. So you have 2 options:
1 - use objects instead of strings, which is kind of more real-world like. Objects have properties that are unique, and not supposed to change, like id for example, or might be something else. For example [{name: 'ABCDE', id: 1}, {name: 'PQRST', id: 2}]. And so you build filtering, deleting etc on them.
2 - Stay with using strings for a while, but rather rely on the index of each element of the initial array.
Here is what I mean:
{this.props.availableCoupon
.filter(
item =>
`${item}`
.toUpperCase()
.indexOf(this.props.searchTerm.toUpperCase()) >= 0
)
.map((item, index) => {
return (
let ind = this.props.availableCoupon.indexOf(item);
<Col key={index} className="mt-3" md="2" lg="2" sm="3" xs="4">
<Card onClick={() => this.props.changed(ind)}>
<CardBody>
<CardTitle>{item}</CardTitle>
</CardBody>
</Card>
</Col>
);
})}
The second one is a realy quick fix but in real world you usually do not do that. And you probably won't need that as long as in real world we have stable id of each object and with it.
As you can see, using the index of the array as a key for each item is unreliable, since the item's key will change whenever its position in the array changes, therefore making the key pretty much useless.
What you should do instead is make it so that your items are objects with unique IDs associated with each one, so that the ID of each item will always match with the item you're working with. Wherever you're using index, use item.id instead.
availableCoupon: [
{name: 'Coupon A', id: 0},
{name: 'Coupon B', id: 1},
{name: 'Coupon C', id: 2},
{name: 'Coupon D', id: 3},
],
nonAvalailable: [
{name: 'filter0', id: 4},
{name: 'filter1', id: 5},
{name: 'filter2', id: 6},
{name: 'filter3', id: 7},
],
Make sure to update the rest of your code accordingly, e.g. by doing <Col key={item.id}>, filtering by item.name instead of item, and so on.
Related
I'm trying to have the title of my products be the choices of my Choice List. In the Shopify documentation its supposed to be like this
<ChoiceList
title="Company name"
choices={[
{label: 'Hidden', value: 'hidden'},
{label: 'Optional', value: 'optional'},
{label: 'Required', value: 'required'},
]}
selected={selected}
onChange={handleChange}
/>
My code is like this
<ChoiceList
choices={
[
data.forEach((element) => (
{ label: `${element.title}` }
))
]
}
selected={selectedExport}
onChange={handleSelectedExport}
/>
But this code does not seem to work. How should I change my code?
You should be using map instead of forEach since you want to create a new array (forEach returns undefined). Also, you don't need to wrap it in brackets, as it's already an array:
<ChoiceList
choices={data.map((element) => ({ label: `${element.title}` }))}
selected={selectedExport}
onChange={handleSelectedExport}
/>;
Side note: if element.title is already a string, you should just use { label: element.title } or if you're feelin' fancy, destructure and use shorthand initialization:
choices={data.map(({ title: label }) => ({ label }))}
If it is not a string, consider using element.title.toString() or another explicit method of converting it to a string.
So here's my problem, i map some data i receive from the back, it returns a group of div, i would like to be able to click a div, change his color background and use it in total price (they're options you can choose).
i tried to put a state "clicked" which set true on click, but the state is on all element ans not the only one i just clicked. After if my state is true, i change the background color and add it to the total price (calculated in the modal in details)
<p className="title-config">Configuration</p>
{data &&
data.additionalCharges.map((charges, index) => {
// console.log("charges.map", charges);
return (
<div
className={
clicked === true ? "clicked-config" : "unclicked-config"
}
key={index}
onClick={() => setClicked(true)}
>
<p>{charges.title}</p>
<p>{charges.description}</p>
<p>
{charges.price.amount} {location.state.price.currency}
</p>
</div>
);
})}
</div>
<div className="colonne2-config">
<div>
<span> Total {location.state.total}</span>
<span>{location.state.price.amount}</span>
</div>
<div>
<div onClick={() => setShowModal(true)}>Voir les details du prix</div>
<Modal
isOpen={showModal}
onRequestClose={() => setShowModal(false)}
style={{
overlay: {
backgroundColor: "lightgrey",
backgroundOpacity: "50%",
},
}}
>
<h1>Details du prix</h1>
<button onClick={() => setShowModal(false)}> X </button>
</Modal>
</div>
Here is a working example to achieve the desired objective:
Code Snippet
const {useState} = React;
const SomeComponent = ({data, ...props}) => {
// the clicked is being used to achieve two goals
// 1. track which item is clicked (ie, selected)
// 2. update the total-price by adding / subtracting the clicked item's price
// NOTE: This is not a good approach to employ in general. Please avoid.
// Instead, use a separate variable to calculate the total-price.
const [clicked, setClicked] = useState({total: 0});
const getClass = idx => (`item ${clicked[idx] ? 'selected' : 'unselected'}`);
return (
<div>
<h4>List of Items</h4>
{
data && Array.isArray(data) && data.map(
({title, description, amount}, idx) => (
<div
key={idx}
onClick={() => setClicked(prev => ({
...prev,
total: (
prev[idx] ? prev.total - +amount : prev.total + +amount
),
[idx]: !prev[idx]
}))}
class={getClass(idx)}
>
{title} {description} {amount}
</div>
)
)
}
<br/>
Total Price: {clicked.total}
</div>
);
};
const rawData = [
{title: 'Title 00', description: 'Description 00', amount: '100'},
{title: 'Title 01', description: 'Description 01', amount: '110'},
{title: 'Title 02', description: 'Description 02', amount: '120'},
{title: 'Title 03', description: 'Description 03', amount: '130'},
{title: 'Title 04', description: 'Description 04', amount: '140'},
{title: 'Title 05', description: 'Description 05', amount: '150'},
{title: 'Title 06', description: 'Description 06', amount: '160'}
];
ReactDOM.render(
<div>
<h2>DEMO</h2>
<SomeComponent data={rawData}/>
</div>,
document.getElementById('reactdiv')
);
.item {
border: 2px solid black;
margin-bottom: 10px;
padding: 2px 15px;
cursor: default;
width: fit-content;
}
.unselected { background-color: #EEEEFF; }
.selected { background-color: #6666AA; color: white}
<div id='reactdiv'/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
The clicked needs to be a data-structure that can track which of the items rendered are clicked (ie, selected) and which are not.
In this snippet, it is set as an object
For simplicity of the demo, the same clicked object serves a secondary purpose of holding the total price.
When user clicks on any item, it's background color changes (using getClass method)
And, the price of the item is added to or removed from total
Overall - this is a fairly simple, straight-forward code snippet.
As per my understanding of the question what you can do is, add another state called divIndex and change the condition to clicked && divIndex === index or you can just remove the clicked state and only use the divIndex state also like divIndex === index.
so i arrived to some kind of solution but it's not perfect, in my newOption, it does not remove the element i clicked on sometimes... it works perfect to add an element in the tab though...
here what i did.
const [selectedOption, setSelectedOption] = useState([]);
const [isSelected, setIsSelected] = useState(false);
const handleConfigClick = (charges, index) => {
const newOption = [...selectedOption];
// Est-ce que l'option est déjà présente ?
// console.log("index", index);
const exist = newOption.find((elem) => elem.id === charges.id);
// console.log("L'élément trouvé ====> ", exist);
if (exist) {
newOption.splice(index, 1);
setIsSelected(false);
console.log("exist");
} else {
console.log("existe pas");
setIsSelected(true);
newOption.push(charges);
}
console.log("newoption", newOption);
setSelectedOption(newOption);
};
it's probably not opti but it's close to my result, just need to resolve the case when the option does not leave the tab on click
I'm working on a comment system and used a recursive approach to show parent and child comments. When I delete a child, I want to update the count of child comments for its parent.
//json data
data = [{
name: 'Parent1',
childComments: [{
name: 'Child1',
text:'child comment1'
},
{
name: 'Child2',
text:'child comment2'
}]
},{
name: 'Parent2',
childComments: [{
name: 'Child1',
text:'child comment1'
},
{
name: 'Child2',
text:'child comment2'
}]
}]
const Comment = ({ item }) => {
const childComment = item
//delete child element and update the parent count
const deleteChildComment =(item) => {
}
return childComment && (
<>
{name}
Children count : {childComments.length}
<Button
className={styles.replies}
onClick={() => {
setNewCommentAdded(false)
deleteChildComment(childComment)
}}
> Delete This comment </Button>
{childComments && items.map((item) => (
<Comment item={item} />
))}
</>
)
}
Counting the children would be a bad idea and make things way too complicated.
Just use the data you use to generate the comment component.
As a rule, always make a UI represent data. Don't try to derive data from your UI layout.
e.g
commentList.map((comment) => (<Comment childCount={comment.children.length}));
Hi all I have following code: my code
In this scenario I am receiving some data from backend
const attachments = [
{
id: 1,
name: "someURLL_Name_1",
link: "https://someURLL_Name_1",
img: "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg"
},
{
id: 2,
name: "someURLL_Name_2",
link: "https://someURLL_Name_2",
img: "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg"
},
{
id: 3,
name: "someURL_Name_3",
link: "https://someURLL_Name_3",
img: "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg"
}
];
I need to map them all and show only first element form my data, and show with numbers rest hided data.
In the end it should be like something like this:
someURL_Name_1 https://someURLL_Name_1 +2 more
I successfully mapped all my data and write little logic for + more.
<div className={Styles.attachments}>
{data.map((item) => {
return <Attachment key={item.id} data={item} image={item.img} />;
})}
{data.length > 1 && (
<span className={Styles.more}>+{data.length - 1} more</span>
)}
</div>
Please help me to resolve a problem. Again, I want to show only first element , and then if there are another elements then I should hide them and show hide elements with numbers.
Thanks.
Just don't map over all entries then. The following will work :-
export const Attachments = ({ data }) => {
return (
<div className={Styles.attachments}>
{data[0] && (
<Attachment key={data[0].id} data={data[0]} image={data[0].img} />
)}
{data.length > 1 && (
<span className={Styles.more}>+{data.length - 1} more</span>
)}
</div>
);
};
i'm pretty new to react and javascript and i want to know how to iterate a list inside another list.
I have this json variable:
this.state = {
groups: [{
name: '',
permission: [{
name: ''
}]
}]
}
What I want to do is to count the number of elements the second list has, i did this following code but it sends the follwoing error, groups.map is not a function:
render() {
let permissionGroup = this.state.groups.map((groups) => {
let x = 0;
groups.map((permission) => {
x++;
})
})
return (
<div>
{this.state.groups.map((groups) => {
return (<li key={groups.code}>
<Card style={{ width: '20rem' }}>
<Card.Body>
<Card.Title>{groups.name}</Card.Title>
<Card.Subtitle className="mb-2 text- enter code heremuted">Permissions info</Card.Subtitle>
<Card.Text>
This group has {permissionGroup} permissions in it.
</Card.Text>
<Card.Link href="#">Add Permission</Card.Link>
<Card.Link href="#">Remove Permission</Card.Link>
</Card.Body>
</Card>
</li>)
})}
</div>
Inside your first map, it should be groups.permission.map, not groups.map.
let permissionGroup = this.state.groups.map((groups) => {
let x = 0;
groups.permission.map((permission) => {
That's because your first map is defining each "groups" as this:
{name: '', permission: [{name: ''}]}
So groups in this sense is an object, not an array. Access permission to get to the array you want.