React JS for loop inside rendering - javascript

I'm new to React JS and I'm not sure how to do a for loop to render something a variable number of times. This is my code:
<div className="product-selector__products">
{ this.props.products.sort(function(a,b) { return a.ranking - b.ranking }).map((p) => {
const className = "product" + ((this.props.selectedProductIds.indexOf(p.id) !== -1) ? " product--selected" : "");
const descriptionHtml = { __html: p.description };
const nameHtml = { __html: p.name };
return (
<div className={className} key={ p.id } onClick={this.onProductClick.bind(this, p.id)}>
<div className="product__image">
<img src={`/~/media/Bayer CropScience/Country-United-States-Internet/Comparison Tool/img/logos/${p.id}_sm.png`} alt={p.name} />
</div>
<div className="product__info">
<div className="product__name" dangerouslySetInnerHTML={nameHtml}></div>
<div className="product__description" dangerouslySetInnerHTML={descriptionHtml}></div>
</div>
<div className="product__message" ref={ p.id }>
<div className="product__message-tooltip">Please remove a product<br/>before adding another</div>
<div className="product__message-triangle-down"></div>
</div>
</div>
);
}) }
/* Here I want to render <div className="product product--empty"> </div> a variable number of times*/
</div>
It generates a grid of product items, with 4 items per row.
I need to add empty divs onto the end of the last row so that each row has the same number of divs in it.
So if this.props.products.length == 7 I need 1 empty div, and if I have 5 products I need 3 empty divs, etc.
The script i want is this:
let remainder = 4 - (this.props.products.length % 4);
for (let i = 0; i < remainder; i++){
return ( <div className="product product--empty"> </div> )
}
I'm not sure how to properly put this into the code-block though.

I've just modified a little Your code.
renderRemainders() {
let products = []
let remainder = 4 - (this.props.products.length % 4)
for (let i = 0; i < remainder; i++){
products.push( <div className="product product--empty"> </div> )
}
return products
}
And just put
{ this.renderRemainders() }
somewhere in Your 'render' function.
Also, could You say something about why you need to render these empty rows?

Related

How do I use For Loop in JavaScript to show the list?

I am a beginner in JavaScript and I can't figure out the following problem: I am trying to create a simple JavaScript Movie List. I have 10 lists on the Movie List. I tried to show all of the lists with for loop, but it doesn't work.
Here's the code:
function renderModal() {
for (let i = 0; i < listMovies.length; i++) {
let movieData = listMovies[i];
document.getElementById("poster").src = movieData.img;
document.getElementById("title").innerHTML = movieData.name;
document.getElementById("genre").innerHTML = movieData.genre;
document.getElementById("rating-num").innerHTML = "Rating: "+ movieData.rating + "/10";
document.getElementById("movie-desc").innerHTML = movieData.desc;
document.getElementById("imdb-page").href = movieData.link;
return movieData;
}
}
What do I have to do?
Help me to fix it!.
You can use template tag for list and render it into target element.I am showing an example.
Movie list
<div id="movieList"></div>
template for list
<template id="movieListTemplate">
<div class="movie">
<img src="" class="poster" alt="">
<div class="title"></div>
<div class="genre"></div>
<div class="rating-num"></div>
<div class="movie-desc"></div>
<div class="imdb-page"></div>
</div>
</template>
Javascript code:
if (listMovies.length > 0) {
const movileListTemplate = document.getElementById('movieListTemplate')
const movieRenederElement = document.getElementById('movieList')
for(const movie of listMovies) {
const movieEl = document.importNode(movileListTemplate.content, true)
movieEl.querySelector('.poster').src = movie.img
movieEl.querySelector('.title').textContent = movie.name
//use all queryselector like above
}
}
Your return movieData; will stop the loop dead. Not that running it more than once will change anything since you change the same elements over and over. IDs must be unique.
Here is a useful way to render an array
document.getElementById("container").innerHTML = listMovies.map(movieData => `<img src="${movieData.img}" />
<h3>${movieData.name}</h3>
<p>${movieData.genre}</p>
<p>Rating: ${movieData.rating}/10</p>
<p>${movieData.desc}
IMDB
</p>`).join("<hr/>");
With return movieData, the for loop will ends in advance.You should put it outside the for loop.

How to create a React class for even and odd numbers

**I have a code that displays the news of the day. https://ibb.co/QMLY2Kx I have 10 classes named "block". Inside the "block" class there are two classes named "blockText". I need to get two different class names and not the same, I want to get this result "blockText1" and "blockText2". How to do it? **
import React from 'react';
import newsStyle from './News_module.css';
export class News extends React.Component {
render() {
const resultsRender = [];
for (var i = 0; i < this.props.news.length; i += 2) {
resultsRender.push(
<div class="block">
{
this.props.news.slice(i, i + 2).map((news, index) => {
return (
<div class="blockText" key={index}>
<p class="text">{news.title}</p>
{console.log(this.props.news.length)}
</div>
);
}
)
}
</div>
);
}
return (
<div>
<div className="headlineSecond">
<div className="Second">
{resultsRender}
</div>
</div>
</div>
);
}
}
You can use ternary operator for this . Here is an example where i chose the value of class based on the value of index and deciding upon whether it is even or odd
<div class={ index%2 ===0 ? "blockText1": "blockText2" } key={index}>
..... rest of code
</div>

How to check if 2 <img> tags contain the same image

I am making a Memory cards game(where cards are flipped on their back and you must open and find the pairs)
I have 12 divs with 12 images and there 6 pairs of images. How can I write JS or jQuery code to check if the images are the same ?
I added data index to divs with setAttribute but on console.log they print undefined.
<div class="frontCard">
<img src="frontCard1.jpg" alt="">
</div>
<div class="frontCard">
<img src="frontCard2.jpg" alt="">
</div>
<div class="frontCard">
<img src="frontCard1.jpg" alt="">
</div>
and js code
const item = document.querySelectorAll(".card");
item.forEach((item, index) => {
item.setAttribute("data-index", index);
})
var openCards = 0;
var points=0;
$(".card").click(function() {
if (openCards >= 2) {
$(".card").removeClass('cardOpen');
$(this).addClass('cardOpen');
openCards = 1;
} else {
$(this).addClass('cardOpen');
openCards++;
if (openCards == 2) {
if (true)//this is where i need the condition
{
const openCard = document.querySelectorAll(".cardOpen");
console.log("index:" + openCard.index);
points++;
console.log(points);
}
}
}
});
basically there are multiple things wrong in your js.
First of - shouldn't .card be .frontCard?
I got a code which is working as expected, even though it might not be the most beuatiful.
Check out the snippet
const item = document.querySelectorAll(".frontCard");
item.forEach((item, index) => {
item.setAttribute("data-index", index);
})
var openCards = 0;
var points=0;
$(".frontCard").click(function() {
if (openCards >= 2) {
$(".frontCard").removeClass('cardOpen');
openCards = 1;
$(this).addClass('cardOpen');
} else {
$(this).addClass('cardOpen');
openCards++;
if (openCards == 2) {
if ($($(".cardOpen")[0]).find("img")[0].src == $($(".cardOpen")[1]).find("img")[0].src){
const openCard = document.querySelectorAll(".cardOpen");
console.log("index:" + openCard.index);
points++;
console.log(points);
$(".frontCard").removeClass('cardOpen');
}else{
console.log("NoPoints")
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="frontCard">
<img src="https://seomofo-da30.kxcdn.com/wp-content/uploads/2010/05/google_logo_new.png" alt="test">
</div>
<div class="frontCard">
<img src="https://i.dlpng.com/static/png/9013_preview.png" alt="test2">
</div>
<div class="frontCard">
<img src="https://seomofo-da30.kxcdn.com/wp-content/uploads/2010/05/google_logo_new.png" alt="test3">
</div>
Assuming that your cards are using the same image, you could check against that. This would require adding ID fields to each card in order to compare the individual cards.
A better way to do this would be to write some JavaScript to contain the cards in an array and fill the array with some ints such as [0,0,1,1,2,2...]. You could then use that to shuffle the cards, or display them and check their values. Just leave yourself a comment as to which number corresponds to which card:)
This would also allow you to display them dynamically if you always start with the back image and then replace the element by ID with the appropriate new image (or lay it over top of it to hide it)

React JSX for loop shows only the last value

I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}

Sort child div based on data attribute

Trying to sort children div based on data attributes
The html code below is being generated by a CM and the data can be retrieved in any random order.
the html code is
<section class="box explore">
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>B</h>
<p>Category_A</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>A</h>
<p>Category_A</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>C</h>
<p>Category_A</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>E</h>
<p>Category_B</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>D</h>
<p>Category_B</p>
</div>
</div>
java
$('div').sort(function (a, b) {
var contentA = $(a).attr('data-name');
var contentB = $(b).attr('data-name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
})
Jsfiddle http://jsfiddle.net/w8gkshue/
if someone can point me in the right direct on how to best sort either by Product Name or Category.
Updated hope this gives better explination
EDIT: I missed the jQuery tag... leaving the answer still.
var productCt = document.getElementById('ProductContainer'),
reInsertProductCt = tempRemove(productCt);
[].slice.call(productCt.children)
.sort(function (a, b) {
var aName = a.dataset.name,
bName = b.dataset.name;
return aName < bName? -1 : +(aName > bName);
})
.forEach(productCt.appendChild.bind(productCt));
reInsertProductCt();
function tempRemove(el) {
var parent = el.parentNode,
nextSibling = el.nextSibling;
parent.removeChild(el);
return function () {
if (nextSibling) parent.insertBefore(el, nextSibling);
else parent.appendChild(el);
};
}
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>TEST NAME B</h>
<p>TEST</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>TEST NAME A</h>
<p>TEST</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>TEST NAME C</h>
<p>TEST</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>TEST NAME E</h>
<p>TEST</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>TEST NAME D</h>
<p>TEST</p>
</div>
</div>
You can use .sort method like this
var $wrapper = $('#ProductContainer');
$wrapper.find('.explore-cell').sort(function (a, b) {
return a.getAttribute('data-name') > b.getAttribute('data-name');
})
.appendTo( $wrapper );
But I don't sure about the cross browsing support
Calling only sort on them won't actually visually change the DOM, it just returns a sorted collection. So basically you just need to get the collection, sort it, then return it. Something like this should work:
$('#ProductContainer > div').detach().sort(function (a, b) {
var contentA = $(a).data('name');
var contentB = $(b).data('name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
}).appendTo('#ProductContainer');
You'll want to make sure that you use the detach() method and not remove(), as detach() will retain all of the data and events associated with the collection items.
Why choose to sort by category or by name when you can sort by both?
I tried to write a generic multisort function generator, which should also work with the native array sort function.
JSFIDDLE HERE
A function that generates the multisort, it takes two parameters.
The column priority list order (first by category or by name? You decide).
I also wanted a way to provide values for columns (since you might not retrieve them the same way for each of them), it is an object that describes for each column a function to retrieve data.
Here it is
function getMultisortFn(columns, provideColumnData) {
return function (a, b) {
for (var i = 0, l = columns.length; i < l; i++) {
var column = columns[i];
var aColumnData = provideColumnData[column.name](a, column.name);
var bColumnData = provideColumnData[column.name](b, column.name);
if (aColumnData !== bColumnData) {
if (column.asc) {
return String.prototype.localeCompare.call(aColumnData, bColumnData);
}
return String.prototype.localeCompare.call(bColumnData, aColumnData);
}
}
};
}
Now this is the part where you actually use the multisort generated
function retrieveDataAttribute(item, attribute) {
return $(item).data(attribute);
}
var $container = $('#ProductContainer');
var $products = $container.find('div');
var multisort = getMultisortFn([{
name: 'category',
asc: false
}, {
name: 'name',
asc: true
}], {
name: retrieveDataAttribute,
category: retrieveDataAttribute
});
$products.sort(multisort);
And finally the DOM manipulation to apply the new order
$products.detach().appendTo($container);
EDIT thanks to plalx:
$container.detach().append($products).appendTo('section.box.explore');

Categories