For Loop not showing the right result in React - reactjs

I have a for loop that needs to render divs according to some logic:
let allRecords;
if (this.state.allRecords) {
allRecords = (() => {
let paramOne = this.state.paramOne;
let paramTwo = this.state.paramTwo;
let paramThree = this.state.paramThree;
let paramFour = this.state.paramFour;
let paramFive = this.state.paramFive;
let paramSix = this.state.paramSix;
for (let i = 0; i < this.state.length; i++) {
let res;
res = (
<div>
<div className="event-result-table-container">
<div className="result-cell">{paramOne[i]}</div>
<div className="result-cell">
<span>{paramTwo[i] ? "Win" : "Lose"}</span>
</div>
<div className="result-cell">{paramThree[i]}</div>
<div className="result-cell">{paramFour[i]}</div>
<div className="result-cell">{paramFive[i]}</div>
<div className="result-cell-last">{paramSix[i]}</div>
</div>
</div>
);
return res;
}
})();
}
The six param arrays all have a similar structure. For example, I have copied the paramSix state array from the Chrome console, and it looks like this:
[
[
479,
480,
481,
482,
483,
484,
485,
486,
487
]
]
Expected Result: I want to see a bunch of res blocks rendered (equal to this.state.length), each with its correct value taken from the state arrays.
Observed result: Only one row of blocks gets rendered, but all the values are there (checked with React DevTools), as if it renders only one block of divs, but puts all the values in their cells, instead of putting them in separate blocks.
What am I doing wrong here?

The first time you call return in your function it stops executing and returns that value. You're calling it within the for loop, but it doesn't matter because for loops don't work that way, they aren't functions. So the loop doesn't run more than once.
The other issue that you have is that your data, like paramSix that you have showed us, is an array with an array of data, so when you reference paramSix[i] in the first (and only) execution of the loop, you are referencing an array, not a number. Instead you probably want paramSix[0][i].
Here's a way to rewrite it so that an array of elements is created. Please note that I used object destructuring to simplify getting the params from the state, and used const since these values shouldn't be changed here:
let allRecords = [];
if (this.state.allRecords) {
const {
paramOne,
paramTwo,
paramThree,
paramFour,
paramFive,
paramSix
} = this.state;
for (let i = 0; i < this.state.length; i++) {
allRecords.push(
<div>
<div className="event-result-table-container">
<div className="result-cell">{paramOne[0][i]}</div>
<div className="result-cell">
<span>{paramTwo[0][i] ? "Win" : "Lose"}</span>
</div>
<div className="result-cell">{paramThree[0][i]}</div>
<div className="result-cell">{paramFour[0][i]}</div>
<div className="result-cell">{paramFive[0][i]}</div>
<div className="result-cell-last">{paramSix[0][i]}</div>
</div>
</div>
);
}
}

Related

React Loop Through FileList and Display

I am trying to loop through a FileList in React and not having any luck.
I have read this article on Can't use forEach with Filelist and with it's help I am able to print to the console. And this Loop inside React JSX article to help with the loop part however I am not able to display any results.
renderEligibilityDocs(e) {
var proctorDocChanges = this.state.proctorDocChanges;
var files = proctorDocChanges[313];
console.log("files", files);
if (files) {
var post = Array.from(files).forEach(file => {
return (
<div key={file.name}>
<h2>file: {file.name}</h2>
</div>
);
});
Array.from(files).forEach(file => console.log("Print to Console " + file.name));
return <div>
{post}
</div>;
} else {
return <div>
<span>No Files Uploaded</span>
</div>;
}
}
What is the concept that I am missing to display the files in the H tag?
If you want to capture or render the output you should use map instead of forEach.
forEach executes a function for each element but it doesn't do anything with the return values, whereas map builds an array from them.
if (files) {
return Array.from(files).map(file => {
return (
<div key={file.name}>
<h2>file: {file.name}</h2>
</div>
);
});
}
else {
...
}
The forEach method doesn't return anything. This is fine for your second loop where you just want to do a console.log, but your first loop needs to return something - you should use map there.
You can also move the map statement into the return statement:
if (files) {
return (
<div>
{Array.from(files).map(f => (
<h2 key={f.name}>file: {f.name}</h2>
)}
</div>
)
}

React. Why is the return inside a map, not the other way around?

I'm learning react and reading this code: https://github.com/john-smilga/react-advanced-2020/blob/master/src/tutorial/4-forms/final/1-controlled-inputs.js (long...)
What I don't understand is this part (line 53-61):
{people.map((person, index) => {
const { id, firstName, email } = person;
return (
<div className='item' key={id}>
<h4>{firstName}</h4>
<p>{email}</p>
</div>
);
})}
Why is the return inside a map ? Since the map is taking items from the array and operating on them individually, won't there be multiple returns for each operation ?
Thank very much !
const l = [1, 2, 3]
const mapped_array = l.map(li => {
console.log(li) // 1, 2, 3
return 10 * li
})
console.log(mapped_array)
/// [10, 20, 30]
You don't need to destructure person properties, you can also use the other way-
{people.map((person, index) =>
(
<div className='item' key={person.id}>
<h4>{person.firstName}</h4>
<p>{person.email}</p>
</div>
))}
You can read more about Array#map in the MDN docs.
map calls a provided callbackFn function once for each element in an array, in order, and constructs a new array from the results.
So the function that is called for each array element must return something. That return value is then used to construct the new array.
This syntax:
() => { /* ... */ }
creates a function. The return statement inside of it only returns that small function that was passed to the map() function, but doesn't return from the outer function.

Is there a react method to dynamically add items from an array to a div in a custom pattern

I am using a div that contains an image below which rows are added with products in them. The product gallery must look like a pyramid so below the image there will be three product rows each containing one, two and three products (each product being a custom rfce), respectively.
Illustration:
img
div1 => product
div2 => product product
div3 => product product product
I used a function that renders a string with all the desired attributes and tried using parse (from html-react-parse) to convert the string to html tags but this method seems slightly unprofessional in addition to which upon attempting to parse the string (which was formatted correctly), I am getting an error.
How can I rectify this, and is there a better method I can use?
string generation method:
const getProducts = () => {
var cur = 0
var gallery = ``
for(var i = 1; i <= 3; i++) {
var prods = ``
for(var j = 0; j < i; j++) {
const product = products[cur++]
// <Product /> is a custom rfce.
prods += `<Product
id = ${product.id}
pname = ${product.pname}
price = ${product.price}
rating = ${product.rating}
pic = ${product.pic}
altPic = ${product.altPic}
/>`
}
gallery += `<div className = 'product_row'>
${prods}
</div>`
}
return ({gallery})
}
method call:
return (
...
<div className = 'product_container'>
<img src = {banner} alt = 'banner' />
{parse(getProducts())}
</div>
...
)
the string is displayed just fine in the body before using parse but upon using it I get the following error after the line const product = products[cur++] in getProducts():
TypeError: product is undefined
Even if I get parse to work, I'd like to use another method as I'm sure there is a cleaner alternative.
Since you know the shape (JSX) you want, I would just hardcode the array chunks. Create a function to render an array into a row, and use the utility for each row, slicing the products state into sub-arrays to be rendered into the rows.
Array.prototype.slice
The slice() method returns a shallow copy of a portion of an array
into a new array object selected from start to end (end not included)
where start and end represent the index of items in that array. The
original array will not be modified.
const renderRow = row => (
<div className='product_row'>
{row.map(product => (
<Product
key={product.id}
id={product.id}
pname={product.pname}
price={product.price}
rating={product.rating}
pic={product.pic}
altPic={product.altPic}
/>
))}
</div>
);
return (
...
<div className = 'product_container'>
<img src={banner} alt='banner' />
{renderRow(products.slice(0,1))}
{renderRow(products.slice(1,3))}
{renderRow(products.slice(3,6))}
</div>
...
);

this.setState is only pulling last value in array

I'm VERY new to React and not sure how to render this nested array from an external JSON file. remittance is the only array here one that has nested values that I need to access and render. It logs to the console fine, but won't separate on setState. I apologize if this looks terrible.
class App extends Component {
constructor(props){
super(props);
this.state = {
data,
rName: "",
rDescription: "",
}
}
componentDidMount(){
console.log("Mounted!");
this.display();
};
display = () => {
for (var i = 0; i < data.length; i++){
var remittanceList = data[i].Remittance;
// console.log(remittanceList);
for (var x = 0; x < remittanceList.length; x++){
var rName = remittanceList[x].PayorName;
var rDescription = remittanceList[x].Description;
console.log(rName + rDescription);
this.setState({rName, rDescription});
}
}
}
render() {
var { rName, rDescription } = this.state;
return (
<div className="App">
<Title/>
{this.state.data.map((each, index) => (
<PayInfo key={index}
name={each.Payee.Name}
fax={each.Payee.Fax}
pan={each.Payment.PAN}
cvv={each.Payment.CVV}
exp={each.Payment.Exp}
payorName={rName}
description={rDescription}
/>
))}
</div>
);
}
}
export default App;
And the JSON file is something like this. Because the amount of remittances can very, I can't hardcode in an index to look for every time.
[
{
"Payee": {
"Name": "Bob",
"Fax": "5555555555",
},
"Payment": {
"PAN": 123456,
"CVV": 123,
"Exp": "1/2018"
},
"Remittance": [
{
"PayorName": "Me",
"Description": "Hello World.",
},
{
"PayorName": "You",
"Description": "Hey world.",
},
{
"PayorName": "Snoop",
"Description": "Bye world.",
}
]
},
And this is the PayInfo.js file I should've posted initially! Not sure if this changes any of the answer I got before?
import React from "react";
import "./PayInfo.css";
const PayInfo = props => (
<div>
<div id="payee">
<p>Payee</p>
<p>{props.name}</p>
<p>Fax: {props.fax}</p>
</div>
<hr></hr>
<div id="payment">
<p>Payment</p>
<p>PAN: {props.pan}</p>
<p>CVV: {props.cvv}</p>
<p>Exp: {props.exp}</p>
</div>
<hr></hr>
<div id="remittance">
<p><strong>Remittance(s)</strong></p>
<p>Payor: {props.payorName}</p>
<p>Description: {props.description} </p>
</div>
<hr></hr>
<hr></hr>
</div>
);
export default PayInfo;
In your display method, you are setting the state inside for loop. so only the last value being set to the state.
Please try the below code.
display = () => {
let totalRemittanceArray = []
for (var i = 0; i < data.length; i++) {
var remittanceList = data[i].Remittance;
for (var x = 0; x < remittanceList.length; x++) {
var rName = remittanceList[x].PayorName;
var rDescription = remittanceList[x].Description;
console.log(rName + rDescription);
totalRemittanceArray.push({ rName, rDescription })
}
}
this.setState({totalRemittanceArray})
}
What you might want to do is something like this:
https://codesandbox.io/s/p36jlrz7jj
As you can see I extracted another component PayerInfo. Also React works perfect with map within the render method. I hope my example helps!
Currently you call setState in the for loop and thus overwrite the state every time, but I guess you figured that one out already. I use Array.map to loop over elements within JSX a lot. Dunno if one could do this more nicely, but there is always a possibility to improve ^^
A small improvement here: You could loop in a separate function first and then return the complete Array: https://reactjs.org/blog/2017/09/26/react-v16.0.html

Calling a function to change css within render() in a React Component

I am returning a set of information from Spotify in a React Component and want to interrogate the JSON that is returned and highlight the original search term within the artist name. so for example, if you search 'bus' and one of the artists returned is Kate Bush, then this would be highlighted green in 'Kate BUSh'. At the moment I am calling a function from within render(). However, what I get rendered is:
Kate <span style="color:green">Bus</span>h
How do I get render() to read the HTML as HTML (so that Bus would just be green) rather than rendering as text? Relevant code from the React Component below:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
if (displayString.indexOf(searchTerm) !== -1) {
displayString = displayString.replace(searchTerm, '<span style="color:green">'+searchTerm+'</span>');
}
return displayString;
}
render() {
return (
<div className="Track" id="Track">
<div className="Track-information">
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
</div>
);
}
Your underlineSearch function needs to return React Elements, but right now it is returning a string. You could use a Fragment to make it work:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
const searchTerm = this.props.searchTerm;
const indexOfSearchTerm = displayString.indexOf(searchTerm);
let node;
if (indexOfSearchTerm === -1) {
node = displayString;
} else {
node = (
<React.Fragment>
{displayString.substr(0, indexOfSearchTerm)}
<span style={{color: 'green'}}>
{displayString.substr(indexOfSearchTerm, searchTerm.length)}
</span>
{displayString.substr(indexOfSearchTerm + searchTerm.length)}
</React.Fragment>
);
}
return node;
}
To make your solution even more reusable you can make underlineSearch and wrapper with your styles for highlighting into 2 separate components. Even more, you can search for multiple occurrences of your searchTerm with regex. Found a similar SO question here. I slightly adapted one of the answers there according to your needs, but all credit goes to this amazing and neat solution for highlighting matches of a string in longer texts. Here is the code:
const Match = ({ children }) => (
<span style={{'color':'green'}}>{children}</span>
);
const HighlightMatches = ({ text, searchTerm }) => {
let keyCount = 0;
let splits = text.split(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let matches = text.match(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let result = [];
for (let i = 0; i < splits.length; ++i) {
result.push(splits[i]);
if (i < splits.length - 1) {
result.push(<Match key={++keyCount}>{matches[i]}</Match>);
}
}
return (
<p>{result}</p>
);
};
Then in your main component where you render everything you can do this:
render() {
<div className="Track" id="Track">
<div className="Track-information">
<h3>
<HighlightMatches text={this.props.trackName} searchTerm={this.props.searchTerm}/>
</h3>
<p>
<HighlightMatches text={this.props.artistName} searchTerm={this.props.searchTerm} /> |
<HighlightMatches text={this.props.albumName} searchTerm={this.props.searchTerm} />
</div>
</div>
}
To me this seems like the most react-like approach to solve the problem :)
While you can use dangerouslySetInnerHTML (), as the name suggests it is extremely dangerous, since it is prone to XSS attacks, for example:
{artist: "Kate Bush<script> giveMeAllYourCookies()</script>"}
You can split the displayString into an array and render it.
Please note that that my implementation of underlineSearch is buggy, and will not work if there are more than one match.
class Main extends React.Component {
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
var index = 0;
var results = [];
var offset = 0;
while(true) {
const index = displayString.indexOf(searchTerm, offset);
if(index < 0) {
results.push(<span>{displayString.substr(offset)}</span>);
break;
}
results.push(<span> {displayString.substr(offset, index)}</span>);
results.push(<strong style={{color: 'green'}}> {displayString.substr(index, searchTerm.length)}</strong>);
offset = index + searchTerm.length;
}
return results;
}
render() {
return <div>
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
}
}
ReactDOM.render(<Main
trackName="Magic Buses"
artistName="Kate Bush"
albumName="Kate Bush Alubm"
searchTerm="Bus"
/>, document.getElementById('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='main'></div>

Resources