In my project, I have 2 arrays being read from Firestore.
courseList - A list of courses of which a user is enrolled in
courses - A list of all of the courses available in the project
I would like to compare these using a .map so that in my course portal, only the courses of which the user is enrolled in is rendered.
Here is what the arrays look like:
courseList:
courses
I know the arrays work, however, the .map doesn't seem to be working!
Here's my code:
const {courses} = this.state
const {courseList} = this.state
{
courses.length && courses.map (course => {
if (course.courseUrl === courseList.CourseCode) {
return (
<div className = "CourseTile" key = {course.courseName}>
<div className = "CourseTitle">
<h1> {course.courseName}</h1>
</div>
<div className = "CourseDescription">
<p> {course.courseSummary}</p>
</div>
<Link to={`/course/${course.courseUrl}/courseinformation`}> <button className = "LetsGoButton"> Take course</button> </Link>
</div>
)
}
else return null;
}
)
}
If I replace
if (course.courseUrl === courseList.CourseCode)
with
if (course.courseUrl === "websitedesign")
It renders the website design course only, So I believe there's something wrong with this line.
Any help would be appreciated.
You are correct in where the problem lies:
course.courseUrl === courseList.CourseCode
In this case course is a single item from a list, with a property courseUrl. That's fine. But courseList is an array of items, each of which has a CourseCode property. The Array itself does not (although, interestingly, it could).
It seems like what you are trying to do is pull the full course data (from courses) but filtered to only the ones the user has. In this case, you have to loop through one list, looking through the other list for each item. What you want is filter (or, more powerfully, reduce) but probably not map.
const filteredCourses = availableCourses.filter( availableCourse => studentsCourses.some( studentsCourse => studentsCourse.id === availableCourse.id ) );
You'll notice I renamed the variables to make it clear which of the two lists is being used at each part.
The outer function filter will return a new array containing only those items that return 'true' in the callback function.
The inner callback function some loops through another array (the student's enrolled courses) and returns true if it finds any that match the given condition.
So in English, "Filter this list of all the courses, giving me back only the courses that have a matching ID in the list of the student's enrolled courses."
if (course.courseUrl === courseList.CourseCode)
You can filter the courses array by checking if each course is included in the courseList array, matching an URL to a courseList element's CourseCode property. array.prototype.some is used to iterate the course list and check that at least one courseList item matches. Once filtered you can map the filtered result as per normal.
const {courses} = this.state;
const {courseList} = this.state;
...
{courses
.filter(({ courseUrl }) =>
courseList.some(({ CourseCode }) => CourseCode === courseUrl)
)
.map((course) => {
return (
<div className="CourseTile" key={course.courseName}>
<div className="CourseTitle">
<h1> {course.courseName}</h1>
</div>
<div className="CourseDescription">
<p> {course.courseSummary}</p>
</div>
<Link to={`/course/${course.courseUrl}/courseinformation`}>
{" "}
<button className="LetsGoButton"> Take course</button>{" "}
</Link>
</div>
);
})}
Related
I have a gallery that displays a number of books per row. This gallery takes an array of books as a prop and uses "itemsPerRow" prop to chunk the books into a 2 dimensional array and then loops through all the books to display the books in a grid-like structure.
export default function Gallery({ items, itemsPerRow, renderLink }) {
itemsPerRow = itemsPerRow ?? 3
const rows = chunk(items, itemsPerRow)
const renderEmptyItems = (itemsToRender) => {
const items = []
for(let n = itemsToRender; n > 0; n--) {
items.push(<GalleryItem key={`empty_${n}`} empty/>)
}
return items
}
return (
<div>
{
rows.map((row, index) => (
<div key={index} className="tile is-ancestor">
{row.map(item => <GalleryItem key={item.id} renderLink={renderLink} {...item}/>)}
{/* Add empty gallery items to make rows even */}
{index + 1 === rows.length && renderEmptyItems(itemsPerRow - row.length)}
</div>
))
}
</div>
)
}
However, unless I give each div representing a row a key, react complains about the lack of keys. As I understand it, using the index as a key doesn't really help react and should be avoided. So what should I use as a key here <div key={index} className="tile is-ancestor"> instead of the index?
Use a unique identifier (book.id, maybe book.title if it's unique) for the key props. If your data does not have a unique identifier, it's okay to use index.
You need to specify a value that uniquely identify the item, such as the id. You can read more about keys in the documentation.
Also it is not recommended to use indexes as keys if the order of your data can change, as React relies on the keys to know which components to re-render, the documentation I linked explains that further.
You can use the unique_identifier which differentiate each of the documents(probably, you should pass the document _id as a key prop in the row components)
<div className="row">
{notes.map((item) => {
return (
<div key={note._id} className="col-md-6">
<Component item={item} />
</div>
);
})}
</div>
I am trying to create a simple shopping cart using ReactJS and I figured a potential way out but whenever I click on the remove button I've set it doesn't really remove the items from the cart..
So those are my state managers right here:
let[product, setProduct] = useState([])
//The function bellow is what I use to render the products to the user
const[item] = useState([{
name: 'Burger',
image: '/static/media/Burger.bcd6f0a3.png',
id: 0,
price: 16.00
},
{
name: 'Pizza',
image: '/static/media/Pizza.07b5b3c1.png',
id: 1,
price: 20.00
}])
and I have a function that adds the objects in item to the product array, then I have a function that is supposed to remove them that looks like this:
const removeItem=(idx)=>
{
// let newProduct = product.splice(idx,1)
// setProduct([product,newProduct])
// $('.showItems').text(product.length)
// product[idx]=[]
product.splice(idx,1)
if(product.length<=0)
{
$('.yourCart').hide()
}
}
This function is called from here:
{product.map((item, idx)=>
<div className='yourCart' key={idx}>
<hr></hr>
<div>
<img src ={item.image}></img>
<h3 className='burgerTitle'>{item.name}</h3>
<h4><strong>$ {item.price}.00</strong></h4>
<Button variant='danger' onClick={()=>removeItem(idx)}>Remove</Button>
</div>
<br></br>
</div>)}
The problem is that I've tried to use splice, setState, I tried to even clear the entire array and add the elements that are left after applying the filter function to it but it was all to no avail.
How can I make it so that when I click on the remove button it removes the specific item from the array??
You need to use the mutation method setProduct provided from the useState hook to mutate product state.
const removeItem = (id) => {
const index = product.findIndex(prod => prod.id === id); //use id instead of index
if (index > -1) { //make sure you found it
setProduct(prevState => prevState.splice(index, 1));
}
}
usage
<Button variant='danger' onClick={()=>removeItem(item.id)}>Remove</Button>
as a side note:
Consider using definite id values when working with items in an array, instead of index in array. the index of items can change. Use the item.id for a key instead of the index when mapping. Consider using guids as identification.
{product.map((item, idx)=>
<div className='yourCart' key={`cartItem_${item.id}`}> //<-- here
<hr></hr>
<div>
<img src ={item.image}></img>
<h3 className='burgerTitle'>{item.name}</h3>
<h4><strong>$ {item.price}.00</strong></h4>
<Button variant='danger' onClick={()=>removeItem(item.id)}>Remove</Button>
</div>
<br></br>
</div>)}
You can define removeItem as a function, which gets an id (instead of an index, since that's safer) and setProduct to the subset which should remain. This could be achieved in many ways, in this specific example I use .filter() to find the subset of product whose elements differ in their id from the one that is to be removed and set the result as the new value of product.
removeItem = (id) => {
setProduct(product.filter((i)=>(i.id !== id)))
}
I'm working on a React portfolio project. I have an external data.js file with an array of objects, which I import into the JS file I'm working in. I want to iterate over the array and place each object into it's own div.
I already tried an If Loop and now working with Map(). This is my code:
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'><ProjectItem /></h1>
</div>
)
}
I don't get the problem of iterating trough an array of objects. This is the error:
Error: Objects are not valid as a React child (found: object with keys {-list of keys-}). If you meant to render a collection of children, use an array instead.
I must overlook a simple thing, but I'm a little stuck at the moment :-)
From the fact you're using project.id (and the error message from React), we can assume that project is an object, which means you're trying to use an object as a React child here:
<div>{project}</div>
The error is that you can't do that. Instead, use properties from the object to come up with how you want it displayed. For instance, if project has a description property:
<div>{project.description}</div>
Or if it has (say) a description and a number of days estimated for the project, you might do:
<div>{project.description} - {project.days} {project.days === 1 ? "day" : "days"}</div>
And so on. The fundamental thing is to provide React with something it can put in the DOM (loosely, "display"), such as strings, numbers, arrays, true (but not false)...
You need to return array from your ProjectItem i.e you need to do this:
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'>{ProjectItem()}</h1>
</div>
)
}
try JSON.stringy(project) like this or you should use your object property.Let's we say project has a property that called name
Here is a working example for you.
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{JSON.stringy(project)} || {project.name}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'><ProjectItem /></h1>
</div>
)
I hope it can work.
you can use project as object into div element
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project.text}</div>
</div>
))
)
I'm changing object property inside mapping and I want to change index when object property is changed ( = input disabled) , so whats best way to do it?
I've tried making new array for index but can't make it work because then it would need some nested mapping with separate arrays and cant make it work.
edit: I use index to mark text part position in official document, thats why this index is so important.
onToggleTextPart = (e, index) => {
const node = this.myCheckbox[index]
let newStates = [ ...this.state.meeting_parts ];
if(node.checked) {
newStates[index - 1].type_tag = "Included";
}
else {
newStates[index - 1].type_tag = "notIncluded";
newStates.splice(index-1, 1)
}
this.setState({ meeting_parts: newStates });
}
return _.map(meeting_parts, (meet, index) => {
let checked = meet.type_tag === "Included" ? true : false;
return
<div className="form-group">
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text minutes-agenda-item-number">
{index} (THIS is the index i want to change)
</span>
</div>
I want to i.e when i hide one object from Index 6, it "gives away" its index and Index 7 takes its position.
Okay as far as I understand you want to do this:
meeting_parts.filter((meet)=>meet.type_tag === "Included").map((meet, index)=>{
// your mapping function here
});
Filter will return an array of the meetings which type_tag is "Included".
You can read about the filter function.
EDIT:
let includedCount = 0;
meeting_parts.map((meet, index)=>{
if(meet.type_tag === "Included") {
includedCount += 1;
}
// your mapping function here but use inlcudedCount instead of index
});
Of course now it displays some numbers mutpliple times. If you don't want them displayed you have to add logic to disable the rendering when necessary.
Issue: I can only render one iteration of my array.
My desired result of course is to get the entire length of array objects.
Adding [key] to my rendered object fields is the only method that gives me any output. Without declaring the key in this way, I get nothing
Child Component
...
const Potatoes = ({potatoes}) => {
const PotatoItems = potatoes.map((potato, key) => {
if ([potato] == ''){
return false
} else {
return (
<li key={key}>
<span>{potato[key].name}</span>
<span>{potato[key].flavor}</span>
</li>);
}
});
return (
<div>
<ul>
{PotatoItems}
</ul>
</div>
);
};
Parent Component
...
render () {
const potatoes = new Array(this.props.potatoes);
return (
<section style={divStyle}>
<Potatoes potatoes={potatoes} />
</section>
)
}
Simply removing new Array() from around the potatoes constant fixes your issue.
It seems like you may have created an unnecessary additional array.
Then you can remove those [key] references on your object in the child component and you should be good to go!
Does this fix your issue?