Iterate in multiple states while rendering - reactjs

I'm trying to render a component that takes data from a local API and it's been set it into multiple states, at the moment I'm mapping through one state and I need to use some data from another state. How can I access this data and map through it once?
In prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/'}
I need to render something like this:
https://www.sodimac.cl/sodimac-homy/product/productID/productLocation
but if I do this:
prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/' + this.state.ids.map(skuMarca => skuMarca.marca)}
It will render something like this:
https://www.sodimac.cl/sodimac-homy/product/productID/productLocation01,productLocation02,productLocation03,productLocation04
UPDATE:
This is the output I'm looking for (once per loop not all of them in one array):
BUTTON 01
BUTTON 02
BUTTON 03
BUTTON 04
I know is confusing so here is my code.
render() {
return (
this.getWebServiceResponse(this.state.ids.map(e => e.sku).join('-'), 96),
this.state.active == true &&
<div className="row" style={{ width: '89%', margin: '0 auto' }}>
<div className="blockCatPriceSlide">
{this.state.products.map(skuData =>
window.innerWidth > 440 && skuData.status === 'OK' ?
<ContenidoUno
prodsName={skuData.name.substr(0, 30) + '...'}
prodsId={skuData.productId}
prodsStatus={skuData.status}
prodsPublished={skuData.published}
prodsNormal={skuData.NORMAL.toLocaleString().replace(',', '.')}
prodsCMR={skuData.CMR}
prodsCombo={skuData.combo}
prodsAhorro={skuData.savings}
prodsStock={skuData.stockLevel}
prodsAntes={skuData.NORMAL + skuData.savings > skuData.NORMAL ? <small> Antes: $ {skuData.NORMAL + skuData.savings} </small> : ''.toLocaleString().replace(',', '.')}
prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/'}
prodsImg={'https://picsum.photos/g/570/250'}
prodsIcon={(skuData.combo === true &&
<img src='https://via.placeholder.com/100x50/f41435/ffffff?text=combo' className="iconic" alt="producto" />) ||
(skuData.CMR !== undefined && <img src='https://via.placeholder.com/100x50/f41435/ffffff?text=CMR' className="iconic" alt="producto" />)}
catName={skuData.webCategoryName}
/> :
<ContenidoUno
prodsName={'Producto sin información...'}
prodsId=''
prodsStatus=''
prodsPublished=''
prodsNormal=''
prodsCMR=''
prodsCombo=''
prodsAhorro=''
prodsStock=''
prodsAntes=''
prodsLink=''
prodsImg={'https://picsum.photos/g/570/250'}
prodsIcon=''
catName=''
/>
)
}
</div>
</div>
)
}
}

Array.prototype.map returns a new array, so in this situation you're just returning array of [marca?, marca?, ...], so instead of returning, marca? from map you should returning whole link like so:
prodsLink={ this.state.ids.map(({ marca }) =>
`https://www.sodimac.cl/sodimac-homy/product/${skuData.productId}/${marca}`
)}
this will generate link array:
[
https://www.sodimac.cl/sodimac-homy/product/${skuData.productId}/${marca},
https://www.sodimac.cl/sodimac-homy/product/${skuData.productId}/${marca},
https://www.sodimac.cl/sodimac-homy/product/${skuData.productId}/${marca},
...
]

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.

MultiSelect in material ui not selecting values properly

I need help with the following code.
The values being selected are messed up. They are not right. Some of the elements get selected twice or more times. some don't get selected at all.
function HelloWorld({
greeting = "hello",
greeted = '"World"',
silent = false,
onMouseOver,
}) {
if (!greeting) {
return null;
}
// TODO: Don't use random in render
let num = Math.floor(Math.random() * 1e7)
.toString()
.replace(/\.\d+/gi, "");
return (
<div
className="HelloWorld"
title={`You are visitor number ${num}`}
onMouseOver={onMouseOver}
>
<strong>
{greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase()}
</strong>
{greeting.endsWith(",") ? (
" "
) : (
<span style={{ color: "grey" }}>", "</span>
)}
<em>{greeted}</em>
{silent ? "." : "!"}
</div>
);
}

How to write the complicated rendering loop in React?

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> )
]
))
])
}

Convert working VueJS component to ReactJS

I have a Vue component that works just fine. Now I'm trying to convert that code to ReactJS equivalent. My attempt on React
var ticksArray = Array.apply(null, {length: 27}).map(Number.call, Number);
export default class Timer extends Component {
constructor(props) {
super(props);
this.state = {
angle:250,
minangle:0,
maxangle:270,
xDirection:"",
yDirection:"",
oldX:0,
dragging: false
}
}
onMousedown(){
this.setState({dragging : true});
}
onMouseup(){
this.setState({dragging : false});
}
onMousemove(e){
if(!this.state.dragging)
return;
this.setState({
xDirection : this.state.oldX < e.pageX ? 'right' : 'left',
oldX:e.pageX,
yDirection: this.state.xDirection === 'left' ? 'down' : 'up'
});
if(this.state.yDirection === 'up' && this.state.angle + 2 <=
this.state.maxangle)
this.setState({angle:this.state.angle += 2})
else if(this.state.yDirection === 'down' && this.state.angle - 2 >=
this.state.minangle)
this.setState({angle:this.state.angle -= 2})
}
knobStyle(){
return {
'transform':'rotate('+this.state.angle+'deg)'
}
}
activeTicks(){
return (Math.round(this.state.angle / 10) + 1);
}
currentValue(){
return Math.round((this.state.angle/270)*100) + '%'
}
componentDidMount(){
document.addEventListener('mouseup',this.state.onMouseup)
document.addEventListener('mousemove',this.state.onMousemove)
}
render() {
var tickDivs = ticksArray.map(function(item) {
return (
<div key={item} className="tick"></div>
);
});
return (
<div id="timer">
<div className="knob-surround">
<div className="knob"></div>
<span className="min">Min</span>
<span className="max">Max</span>
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
{tickDivs}
</div>
</div>
</div>
);
}
}
It's not working. I'm missing something. I'm assuming the problem lies in this code bit.
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
Please help fix this.
Add this here instead of comment:
React uses the following syntax:
className={n <= activeTicks ? 'activetick' : ''}
In componentDidMount you assign handlers in a wrong way, should be like:
document.addEventListener('mouseup', this.onMouseup)
Note here that handler is not a part of your state. And the corresponding definition of the handler:
private onMouseup = () => {...}
The reason to store reference for the event handler instead of having class method - see in #3
Do not forget to unsubscribe your event handlers in componentWillUnmount like this:
window.removeEventListener("mouseup", this.onMouseup);
UPDATE:
Here is an example working without using arrow functions: https://jsfiddle.net/6dnrLw4n/4/

React className with ternary operator add class 'null'

I'm trying to conditionally apply a class to my component using an expression like this:
.map(function(list, index) {
<div className={"myClass " + (position === index ? 'active' : null)}>
}
But it keeps adding null as class, with an end result like this:
<div class="myClass active">...
<div class="myClass null">...
This is a simple example, with only 2 class names, so I could just replace null with the default class name. But in a more complex layout, I would need to duplicate the same name over and over again.
Is there a better approach to solve this problem?
You could use an empty string '' instead of null like:
.map(function(list, index) {
<div className={"myClass " + (position === index ? 'active' : '')}>
}
Also map should return a value:
.map(function(list, index) {
return <div className={"myClass " + (position === index ? 'active' : '')}>;
}
If you have multiple classes, you might consider building the list of classes from an array:
var classes = ["myClass"];
if (position === index) {
classes.push('active');
}
return (
<div className={classes.join(' ')}>
...
</div>
);
You can also consider using a helper function that will generate the className string from an object like this:
var classes = {
myClass: true,
active: position === index
};
classnames is one such utility (not the only one).
Remove the space from "myClass " to "myClass", then replace null with an empty string ""
.map(function(list, index) {
<div className={"myClass" + (position === index ? 'active' : "")}>
}
just use https://www.npmjs.com/package/classnames:
usage example:
<div className={cn({"active": position === index })} ></div>
Use && short-circuiting: className={"myClass " + (position === index && 'active')}
In this way, if position === index is false, because we are using &&, we short-circuit. JS skips over 'active' and we just move on with our lives.
React Solution:
className={`myClass ${index ? "active" : ""}`}
Different syntax
className={`myClass ${index && "active"}`}

Resources