How to write the complicated rendering loop in React? - reactjs

I am writing a nested loop in React. All I am seeing is the final return statements of tags. Where are the and going? Thank you.
{ this.state.data.headings.map( (heading, i) =>
<h3 key={i}>{heading}</h3> &&
// some headings do not have subheadings, tho
// they still have statements. deal with these cases first...
((this.state.data.subheadings[i].length === 0 &&
this.state.data.statements[i].map((statement, _j) =>
<p key={i+_j}>{statement}</p>)) ||
// cases where the group of statements has a subheading...
(this.state.data.subheadings[i].map((subheading, j) =>
<h4 key={i + j}>{subheading}</h4> &&
this.state.data.statements[i][j].map((statement, k) =>
<p key={i+j+k}>{statement}</p>))
)
)
)
}

A better way of doing this in my opinion is to separate this in different components each one of them taking care of one of the loops.in your case header,subheader,statement, etc.

There is everything ok with you code, except you can refactor it to make more readable.
Don't repeat yourself (DRY), always move duplicated code to separate component, in your example it is statement element. Also, i remove redundant key props.
render() {
const {headings, subheadings, statements} = this.state;
return headings.map((heading, i) =>
<div key={i}>
<h3>{heading}</h3>
{
subheadings[i].length
? subheadings[i].map((subheading, j) =>
<div key={j}>
<h4>{subheading}</h4>
<Statements statements={statements[i][j]}/>
</div>
)
: <Statements statements={statements[i]}/>
}
</div>
);
}
const Statements = ({statements}) => (
statements.map((statement, i) =>
<p key={i}>{statement}</p>
)
);

(omg folks,) feels like i had to take a picture to prove it...
solution, special thanks to a similar Q&A (I'm using React v15 out of an older template for Ether dApps)
{ headings.map( (heading, i) =>
[ <h3 key={i}>{heading}</h3>,
subheadings[i].length === 0 ?
statements[i][0].map( (statement, j) =>
<p key={j}>{statement}</p>,
) :
subheadings[i].map( (subheading, j) => (
[<h4 key={j}>{subheading}</h4>,
statements[i][j].map( (statement, k) =>
<p key={k} style={{color: 'green'}}>{statement}</p> )
]
))
])
}

Related

Each child in a list should have a unique "key" prop error despite method to create unique key

In my application I am currently getting the react warning:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of GetReplies.
This is the GetReplies method:
export function GetReplies(props) {
const Id = props.Id;
const replies = allComments.filter(obj => obj.ParentCommentId === Id).
sort((objA, objB) => new Date(objB.Time) - new Date(objA.Time));
console.log(generateKey(Id));
if (Object.keys(replies).length !== 0) {
return (
<div key = {"replies-container-" + generateKey(Id)} id={"replies-container-" + Id} className="replies-container">
<div key ={"panel-heading replies-title" + generateKey(Id)} className="panel-heading replies-title">
<a key = {"accordion-toggle replies-" + generateKey(Id)} className="accordion-toggle replies-a collapsed" data-parent={"#replies-container-" + Id} data-toggle="collapse" data-target={"#replies-for-" + Id}>Replies</a>
</div>
<div key = {"replies-for-" + Id} id={"replies-for-" + generateKey(Id)} className="replies-list collapse">
{
<React.Fragment>
{ Object.entries(replies).reverse().map(([key, arr]) => {
return (
<GetComments commentsArray = {replies}/>
)
}) }
</React.Fragment>
}
</div>
</div>
);
}
}
and this is the GetComments Method it calls:
export function GetComments({ commentsArray }) {
return (
<React.Fragment>
{commentsArray.map((comment) => {
const localId = comment.LocalId;
const parentCommentId = comment.ParentCommentId;
const parentLocalId = allComments.filter(obj => obj.Id === parentCommentId);
const recipients = comment.Recipients;
let recipientsArray = [];
let recipientsList;
recipients.forEach(function (arrayItem) {
recipientsArray.push(arrayItem.Name);
recipientsList = recipientsArray.join(', ');
});
console.log(generateKey(localId));
const date = new Date(comment.Time);
const formattedDate = date.toLocaleDateString() + " " + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2);
return (
<div key={generateKey(localId)} className="comment-container">
<div key={generateKey(comment.Commenter.ItemId)} className="commenter">
<span className="id-label">{localId}</span>
{parentCommentId && (
<span className="reply" title={`in reply to ${parentLocalId[0].LocalId}`}>
<a className="reply" href={"#c" + parentLocalId[0].LocalId}>⤵</a> </span>
)}
<span><a id={"c" + localId} name={"c" + localId}>{comment.Commenter.Name}</a></span>
<div key={generateKey(localId) + "-comment-actions-container "} className="comment-actions-container">
<button type="button" className="btn-reply" data-value={comment.Id} title="Reply to comment" data-toggle="modal" data-target="#dlg-new-comment">⥅</button>
</div>
</div>
<div key={generateKey(localId) + "-recipients "} className="recipients">{recipientsList}</div>
<div key={generateKey(localId) + "-comment "} className="comment">{comment.Comment}</div>
<div key={generateKey(localId) + "-comment-footer "} className="comment-footer">{formattedDate}</div>
<GetReplies Id = {comment.Id}/>
</div>
);
})}
</React.Fragment>
);
}
To help make sure each key is unique I made this generate key method:
const generateKey = (pre) => {
return `${ pre }_${ new Date().getTime() }`;
}
However I am still getting that unique key error and I have no idea what it is I could be missing?
I am even tried to expand upon it by doing:
const generateKey = (pre) => {
let index =0;
return `${ pre }_${ new Date().getTime()}_${index++}`;
}
but index would always equal 0? So I'm not sure why that wasn't incrementing either, any advice would be appreciated
Even though your generateKey might work (not sure though since it will get called quite quickly when mapping over an array) you are adding the key to the wrong part of your code.
React expects a key on every item in a for-loop. In your case you have added a key to every JSX element, except for those within the Object.entries(replies).reverse().map
You could probably fix your problem by adding keys in there, like so:
{Object.entries(replies).reverse().map(([key, arr]) => {
return (
<GetComments key={key} commentsArray = {replies}/>
)
})}
To add a note though, React uses the key value to recognize changes on re-renders. In case your array gets re-ordered it can use the keys to prevent enormous re-renders. The best practice here would be to add a key that is related to the data itself, not the location in an array or a random number.
In addition of the previous answer :
const generateKey = (pre) => {
let index =0;
return `${ pre }_${ new Date().getTime()}_${index++}`;
}
will always have a 0 index, because each function call will create his own scope, with his own "index" variable. To increment the index on each call, you must extract the "index" variable outside of the function, so each function call will share the same index variable, from the 'outside' scope (but using the item's index remains the best idea, if you have no unique key you can use from your data).
Using "new Date().getTime()" to generate keys is also a bad idea, because your code will be ran so quick that a few components could (and will) share the same timestamp.
An alternative is to use a third party library, like 'uuid', to generate unique ids, but it must be used carefully.
You should use the index of the commentsArray.map
like :
{commentsArray.map((comment, index) => {
...
key={generateKey(Id, index)}
and then :
const generateKey = (pre, index) => `${pre}_${index}`;
You are filtering with Id values, so your key will be the same for each childs, that's why you need the commentsArray index.

Object with similar attribute names loop, React

I have object with attributes like tabletA, tabletB, tabletC, containing allways string.
The same object contains other attributes as well, like company, phone.
My goal is to look at attributes in one line, and display only tablet attributes, where are strings, with values.
So I imagine code to look like something like this:
{referenceTexts.[tablet].length > 0 && (
<div>
referenceTexts.[tablet]
</div>
)}
{Object.entries(referenceTexts)
.filter(([key, val]) => key.startsWith('tablet') && val.length > 0)
.map(([_, val]) => (
<div>{val}</div>
))}
const tabletValues = Object.keys(referenceTexts)
// get keys that start with "tablet"
.filter(key => key.startsWith('tablet'))
// get their values
.map(key => referenceTexts[key])
// get only values that are not empty
.filter(value => (value || '').length > 0);
Then
{tabletValues.map(value => <div>{value}</div>)}
Note that the order of the values is undefined so you might want to add some kind of sorting.
hopefully I got you. You want something like this :
{Object.keys(referenceTexts).map((key) => {
if (key.substr(0, 6) === "tablet") {
return <h1>{referenceTexts[key]}</h1>;
}
})}

How can i make an optional field?

I am making a Checkout form but i dont know how can i make it optional? I'm new with react js and i badly need help because i have defense some time next week and this is the only problem that i am encountering with the revisions that they have said.
Anyway here it is.
const objChecker = (e, id) => {
const hasEmpty = Object.values(orderInfo).some(x => x == '' );
console.log(Object.values(orderInfo), hasEmpty, id)
if(hasEmpty){
window.alert('Please input all fields')
return false
} else {
console.log(e, 'e')
setOrderInfo({ ...orderInfo, payment_id: id })
}
}
<div className='checkout_modal_payment_method_wrap'>
{paymentMethods.length > 0 && paymentMethods.map(method => (
<label htmlFor={`payment_method_${method.id}`} key={method.id} className='checkout_modal_payment_method_item'
style={{display: method.id === 1 && product.category_id === 2 && 'none'}}>
<input type='radio' id={`payment_method_${method.id}`} name='payment_id' value={method.id} onChange={(e) => objChecker(e, method.id)} required checked={payment_id === method.id}/>
<div>
{method.payment_name}
</div>
The only fields that i want to declare as optional is the ADDRESS LINE 2 and TYPE OF EVENT only. The rest will be required
This is the UI

React/Gatsby: Conditionally wrap every two posts inside a div

I'm trying to wrap every two posts inside a container div. Below is what I tried, but unfortunately I get an error.
This is what I tried:
I have a variable called postIndex. While the posts are being iterated over with posts.map(({ node }) => {...}, I have a conditional if/else check to see if postIndex is odd or even with if (postIndex % 2 == 0) (check if postIndex is even) and if (postIndex % 2 == 1) (check if postIndex is odd).
If postIndex is even I render out only the opening <div> tag that is the container for the two posts. If postIndex is odd, then I render out only the closing </div> tag.
I get an error with this implementation, though. What is the right way to go about doing something like this?
Example of what I tried:
let postIndex = 0
return (
<Layout>
{posts.map(({ node }) => {
if (postIndex % 2 == 0) {
postIndex++
return (
<div>
<p>test</p>
)
} else if(postIndex % 2 == 1) {
postIndex++
return (
<p>Test</p>
</div>
)
}
})
}
</Layout>
)
An opening tag without a closing tag is invalid JSX. You can probably do something like this below though. Also, you have access to the index of the array in a map, so you don't need to create a new variable.
return (
<Layout>
{posts.map(({ node }, index) => {
if (index % 2 === 0) {
return (
<div key={index}>
<p>{node}</p>
{posts[index + 1] && <p>{posts[index + 1].node}</p>}
</div>
)
}
})
}
</Layout>
)

How can I concatenate strings for a React key?

I am creating a list of list and want to put a unique key for each element. When I use the React Dev Tool, the new key is "2016-10,-,football".
Why does it have commas in it?
What is the correct way to specify a key when I want "2016-10-football"?
React Dev Tool Console
import React from 'react'
import ReactDOM from 'react-dom'
const dates = ['2016-10', '2016-11', '2016-12'];
const sports = ['football', 'baseball', 'basketball'];
const Dates = ( { dates, sports } ) => {
return (
<ul>
{ dates.map( date => {
return (
<div key={date.toString()} >
<li>{date}</li>
<Sports sports={sports} date={date}/>
</div>
)
})
}
</ul>
)
}
const Sports = ( { date, sports } ) => {
return(
<ul>
{ sports.map( sport => {
// Results in: key="2016-10,-,football"
// Expected: key="2016-10-football"
return (<li key={[date, '-', sport]} >{sport}</li>)
})}
</ul>
)
}
ReactDOM.render(<Dates dates={dates} sports={sports}/>, document.getElementById('main'))
key expects a string so when you pass an array you are calling the Array's .toString() function. You will see the same result if you do console.log([date, '-', sport].toString())
Replace [date, '-', sport] with date + '-' + sport to fix it.
It's showing with commas because toString will use commas to join the array.
This is what you have:
arr = ['2016-10', '-', 'football']
console.log(arr.toString); // "2016-10,-,football"
This is what you want:
arr = ['2016-10', '-', 'football']
console.log(arr.join()); // "2016-10-football"
So consider replacing the li to (notice the .join()):
return (<li key={[date, '-', sport].join()} >{sport}</li>)
edit: use join("") for expected result, you should pass a separator (in this case an empty string) to arguments of the method. For example, ['2016-10', '-', 'football'].join('~separator~') would return "2016-10~separator~-~separator~football"
Added some examples for better understanding
key={'company_'+index} // key={date +'-'+sport}
<TableCell key={'company_'+index} align="right">
{row.company?.name}
</TableCell>
return(
<ul>
{ sports.map( sport => {
// Results in: key="2016-10,-,football"
// Expected: key="2016-10-football"
return (<li key={date +'-'+sport} >{sport}</li>)
})}
</ul>
)
I had no problem using a plus sign to concatenate two fields to make a unique key:
{rows.map((Group) => (
<li key={Group.user_id + Group.lgroup_id}>
-- Display the parts of the Group object here. --
</li>
))}

Resources