conditionally rendering className in react jsx - javascript

var array = [
['2','35'],
['80','30'],
['300','25']
]
so this is a simplified version of the array, what i am getting from an api call. Each children array's 1st value is quantity and 2nd value is price. below is my simplified state
this.state = {quantity:''}
Inside jsx what i am trying to do is conditionally render a classname called selected based upon quantity value of the state. whenever the state quantity changes. the selected class also should change accordingly.
below is my jsx
{array.map((price, index, arr) => {
if (index < arr.length -1) {
if (parseInt(arr[index+1][0]) > parseInt(this.state.quantity) && parseInt(this.state.quantity) >= parseInt(price[0])){
return (
<div className='price-box selected'>
<h3 className='price'>Tk {price[1]}</h3>
<p className='quantity'>{price[0]} or more</p>
</div>
);
} else {
return (
<div className='price-box'>
<h3 className='price'>Tk {price[1]}</h3>
<p className='quantity'>{price[0]} or more</p>
</div>
);
}
} else {
if (parseInt(this.state.quantity) >= parseInt(price[0])) {
return (
<div className='price-box selected'>
<h3 className='price'>Tk {price[1]}</h3>
<p className='quantity'>{price[0]} or more</p>
</div>
);
} else {
return (
<div className='price-box'>
<h3 className='price'>Tk {price[1]}</h3>
<p className='quantity'>{price[0]} or more</p>
</div>
);
}
}
})}
Here everything is working fine (apart from for quantity 0 and 1 all of the conditions are evaluating to false as expected.so not a single div is assigned selected class).I am 100% sure there is a shorter and better approach.

Name your data points and construct a test which satisfies all the requirements for selected. Then assign the class name if selected is true using a template literal.
{array.map((price, index, arr) => {
const stateQ = parseInt(this.state.quantity);
const dataQs = arr.map((p, i) => i === 0 ? 0 : parseInt(p[0]));
const selectedIndex = dataQs.findIndex((q, i, arr) => {
return stateQ >= q && stateQ < (arr[i+1] || stateQ + 1);
});
const selected = selectedIndex === index;
return (
<div className={`price-box ${selected && 'selected'}`}>
<h3 className='price'>Tk {price[1]}</h3>
<p className='quantity'>{price[0]} or more</p>
</div>
);
})}

Maybe something like this is what you're looking for ?
Working example on Codesandbox
class Quantity extends React.Component {
constructor(props) {
super(props);
this.state = {
quantity: "2"
};
}
render() {
const array = [["2", "35"], ["80", "30"], ["300", "25"], ["2"], ["", "3"]]; // With some errors
return (
<div>
<h1>Hello</h1>
{array.map((row) => {
const condition = this.state.quantity === row[0]; // Create your condition
if (row && row[0] && row[1])
return (
<div className={`price-box ${condition && "selected"}`}> // Called template literal, you can simply include selected if your condition is true
<h3>Quantity {row[0]}</h3>
<p>Price {row[1]}</p>
</div>
);
else return <div>Data error</div>;
})}
</div>
);
}
}

Related

How to split content in two and add a class

I have a code that displays the news of the day. I want to split my content into two parts, that is, for the first block, create a class named "news1" and for the second "news2" as shown in the picture https://ibb.co/ykv7BN4 How can i do this? here is my code
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 className={newsStyle.block}>
{
this.props.news.slice(i, i + 2).map((news, index) => {
return (
<div className={index % 2 === 0 ? newsStyle.leftContentNews : newsStyle.rightContentNews} key={index}>
<p className={newsStyle.newsTitle}>{news.title}</p>
</div>
);
}
)
}
</div>
);
}
return (
<div>
<div className={newsStyle.headlineSecond}>
<div className={newsStyle.Second}>
{resultsRender}
</div>
</div>
</div>
);
}
}
In this case, you would want a <div> for each column and append the news of each type to the column's div.

JavaScript. React. Redux. Async. Loop does not complete before rendering of DOM

I'm working on a Messagelist component in which I am pulling message data from an external API. Every message contains a UserId in which I'll use to query my own API for more user information. I put the list of foundUsers in my redux store and have connected my component. Before rendering to the DOM, my objective is to loop through each individual message and replace the userid with the name of its corresponding user.
The problem I'm having is that the loop does not appear to finish before rendering the component, giving me some strange behavior.
Half a second later
Here is my code:
render(){
let users = this.props.chatkit.roomUsers
let userIds = users.map((user) => {
return user.id
})
let roomMessages = this.props.messages
let messages = []
for(var i = 0; i < roomMessages.length; i++){
//create a new field to give each message a unique id
roomMessages[i].unique = roomMessages[i].senderId
//create a new field to give each message the avatar from that user
let matchingUserIndex = userIds.indexOf(roomMessages[i].senderId)
if(matchingUserIndex >= 0){
roomMessages[i].avatar = users[matchingUserIndex].avatar
}
//review messages
let previous = {}
if(i > 0){
previous = roomMessages[i - 1]
}
let currentMessage = roomMessages[i]
//check if consecutive messages are made by the same user
if(currentMessage.senderId === previous.senderId && currentMessage.unique === previous.unique){
//display user name and avatar as an empty string
messages.push({...currentMessage, unique: "", avatar: ""})
} else{
//display the user name
messages.push({...currentMessage})
}
}
//transform the unique field to contain the name of the user
let updatedMessages = []
for(var j = 0; j < messages.length; j++){
let matchingIdIndex = userIds.indexOf(messages[j].unique)
if(matchingIdIndex >= 0 && messages[j].unique !== ""){
messages[j].unique = users[matchingIdIndex].name
updatedMessages.push(messages[j])
} else {
updatedMessages.push(messages[j])
}
}
let currentChatkitUser = this.props.chatkit.chatUser.id
return(
<div>
{this.props.room && (
<div
style={{overflow: "scroll", overflowX: "hidden", maxHeight: "70vh"}}
ref={this.messageList}
>
<ul style={{listStyle: "none"}} className="p-4 mb-0">
{updatedMessages.map((message, index) => {
return (
<li
className="mb-1"
key={index}>
<div>
{message.unique && (
<span
className="d-block font-weight-bold mt-3"
style={{color: "#000323"}}
>
<img
src={message.avatar}
style={{width: "2.5rem"}}
className="rounded-circle mr-2"
/>
{message.unique}
</span>
)}
<span
className={message.senderId === currentChatkitUser ?
"muted-blue text-light rounded d-inline-block ml-5" : "bg-secondary text-light rounded d-inline-block ml-5"
}
style={{padding:".25rem .5rem"}}
>
{message.text}
</span>
</div>
</li>
)
})}
</ul>
<TypingIndicator usersWhoAreTyping={this.props.usersWhoAreTyping}/>
<div
style={{float: "left", clear: "both"}}
ref={this.messagesEnd}>
</div>
</div>
)}
</div>
)
}
use state property(e.g. loading = true) at initial which wil return the rendering some loading DOM. and define a function performing all the loop activity and execute it in componentwillmount and when you finish looping set loading property of state to false by which you can make your DOM to render your list of user in render method. ( not sure if componentwillmount is deprecated ).

How to show results of a map in two or more columns using react

I think I have a simple question, but I can't get a solution to do this with react, I would like show results in two columns like:
item 1 | item 4
item 2 | item 5
item 3 | item 6
I tried verify if array lenght is 0 or new start column, but I can't draw a start div element without draw the end div element
I would like to do something like this:
render() {
const secondColumnStart = this.props.result.length / 2;
return <div className="row">
{this.props.result.map((item, i) =>
{ (i == 0 || i == secondColumnStart) && <div className="col-md-6"> }
item.value
{ (i == 0 || i == secondColumnStart) && </div> })}
</div>;
}
Simply map items as you usually do from one array. With that, use the CSS property "columns" to display them as described in the question above.
.container {
columns: 2 auto;
}
Assuming two column's, having col-md-6 class for row splitting.
create stateless component myRow
const myRow = ({index})=>{(<div className="col-md-6">{index}</div>)}
create array for each cols
const col1 = [],col2 = [];
this.props.result.forEach((item, i) => {
if(i%===0){
col1.push(myRow);
}else{
col2.push(myRow);
}
}
return the React element in render.
return <div className="row">
{col1}{col2}
</div>;
If you always want exactly two columns, then one option is to call map twice. Once for each half of the array:
const secondColumnStart = Math.floor(this.props.result.length / 2);
return (
<div className="row">
<div className="col-md-6">
{this.props.result.slice(0,secondColumnStart).map(item => item.value)}
</div>
<div className="col-md-6">
{this.props.result.slice(secondColumnStart).map(item => item.value)}
</div>
</div>
);
Will there always be 2 columns, regardless of how many items you have? If there are 5 items, should it display as col A -> 1,2,3. col B -> 4,5?
Use CSS to put the two columns side by side.
var halfwayPoint = Math.ceiling(this.props.result.length / 2)
var columnA = this.props.result.splice(0, halfwayPoint)
var columnB = this.props.result.splice(halfwayPoint)
render () {
return (
<div className='columnA'>
{columnA.map((item, i) => {
return (
<div>{item.value}</div>
)
})
}
</div>
<div className='columnB'>
{columnB.map((item, i) => {
return (
<div>{item.value}</div>
)
})
}
</div>
)
}
You can use the following code.
const Thingy = props => {
const gridCols = [[],[]];
const result = [10,20,30,40,50,60,70];
result.forEach( ( data,i )=>{
const comp = (
<button value={data}>{data+" "}</button>
);
const colNumber = i % 2;
gridCols[colNumber].push( comp );
} );
return (
<div className="container">
<div className="row">
<div className="col-sm">
{gridCols[0]}
</div>
<div className="col-sm">
{gridCols[1]}
</div>
</div>
</div>
)
};
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I also faced a similar problem where I need to show the results of an array into three columns in card.
For that I have grouped the array elements into groups as below.
arr = ['a','b','c','d',e','f'] --------> arr = [['a','b','c'],['d','e','f']]
let modified_collection=[]
if (collection.length>0) {
modified_collection = collection.reduce( (rows, key, index) =>{
return (index % 3 === 0 ? rows.push([key])
: rows[rows.length-1].push(key)) && rows;
}, []);
}
After grouping I have map the elements in the modified array as below.
modified_collection.map((row) =>
<Row>
{row.map(col => (<Col>
<Card
hoverable
style={{ width: 240, height : 480 }}
cover={<img alt="example" src={col.collection.image_url} />}
>
<Meta title={col.collection.title} description={col.collection.description} />
</Card>
</Col>))}
</Row>
)
The simplest method with few lines of code for your question is
list.map((list, index)=>{
return index % 2 === 0 ?
<Col xs={6}>
{list}
</Col>
:
<Col xs={6}>
{list}
</Col>
})

react.js every nth item add opening tag or closing tag

I'm having trouble with this logic since react/jsx does not allow for non closing tags to be added to an array/child component. For example with bootstrap css I want to add a row for every 4 columns.
So the logic is as follows:
Add a opening row ex: <div className="row">, then loop inside this row and every loop append a column ex: <div className="column>{this.data}</div> when the loop reaches 4 check with if(i % 4 == 0) and add a closing </div> tag while adding new row tag <div className="row">;
The code below would work in another language but in react this is not doable since we push a closing tag and a opening tag (which is invalid jsx):
generateColumns(columns) {
let newColumns = [];
columns.forEach(function(column, idx) {
newColumns.push( <div className="column"> some data </div> );
if (idx % 4 == 0) {
// Here we end the row and start a new row, works in any other language.
newColumns.push( </div> <div className="row"> );
}
});
// This array now has the proper tags for opening a row every 4th item and closing it.
return newColumns;
},
render() {
return (
<div className="row">
{this.generateColumns(this.props.columns)}
</div>
)
}
The expected output would be:
<div class="row">
<div class="column">
Some data
</div>
<div class="column">
Some more data
</div>
<div class="column">
Other data
</div>
<div class="column">
Something else
</div>
</div>
<div class="row">
<div class="column">
Some data
</div>
<div class="column">
Some more data
</div>
<div class="column">
Other data
</div>
<div class="column">
Something else
</div>
</div>
//the above would be repeated and new rows would appear every 4 columns.
render() {
const rows = array_chunk(this.props.columns, 4)
return (
{
rows.map((row) => (
<div className="row">
{
row.map((col) => (
<div className="col">{ col }</div>
))
}
</div>
))
}
)
}
An example array_chunk (I recommend that you use lodash)
module.exports = function chunks(arr, size) {
if (!Array.isArray(arr)) {
throw new TypeError('Input should be Array');
}
if (typeof size !== 'number') {
throw new TypeError('Size should be a Number');
}
var result = [];
for (var i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, size + i));
}
return result;
};
I actually just used arrays and react handled fine the rendering.
render() {
let rows = [],
cols = []
let index = 0
const totalCols = 20;
for (index; index < totalCols; index++) {
cols.push(<div class="col" key={index}/>)
if ((index + 1) % 4 == 0) {
rows.push(
<div class="row" key={index}>
{cols}
</div>
)
cols = []
}
}
return (
<div class="container">
{rows}
</div>
)
}

Reusing properties within map loop in ReactJS - best practice

I am trying to figure out the best way to render two blocks of code in ReactJS, one will be used for desktop and the other for mobile. Functionality wise they will do exactly the same thing but have different markup wrapped around them, an example would be a carousel that renders differently on mobile.
I am using the map function to iterate over the object properties, I have working code below but I am duplicating variables and reassigning the same values which is obviously inefficient as I am doing this for each code block.
Can anyone suggest a nice / best practice way of doing what I need?
Sorry for the basic question!
render: function() {
return (
<div>
<div className="hidden-xs">
{
this.state.userItems.map(function (item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
{someValue}<br>{anotherValue}
</div>
)
})
}
</div>
<div className="visible-xs">
{
this.state.userItems.map(function (item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
<div className="differentMarkup">
{someValue}<br>{anotherValue}
</div>
</div>
)
})
}
</div>
</div>
)}
Updated answer:
If the inner items can have different markup, I would say it depends how different their markup is going to be. For example, you could have two separate methods that prepare the markup of the mobile and non-mobile version of the items separately. Something like this:
renderUserItem: function(itemData) {
return (
<div key={itemData.someValue}>
{itemData.someValue}<br>{itemData.anotherValue}
</div>
)
},
renderMobileUserItem: function(itemData) {
return (
<div key={itemData.someValue}>
<div className="differentMarkup">
{itemData.someValue}<br>{itemData.anotherValue}
</div>
</div>
)
},
prepareItemData: function(item) {
return {
someValue: item.value ? item.value : '';
anotherValue: item.anotherValue ? item.anotherValue : '';
};
},
render: function() {
// Prepare the variables you will need only once
let parsedUserItems = this.state.userItems.map(this.prepareItemData);
return (
<div>
<div className="hidden-xs">
{ parsedUserItems.map(this.renderUserItem) }
</div>
<div className="visible-xs">
{ parsedUserItems.map(this.renderMobileUserItem) }
</div>
</div>
)
}
You could also have a single method if the differences between mobile and non-mobile are not too big. For example, using ES6 arrow functions:
renderUserItem: function(itemData, renderWrapper) {
return (
<div key={itemData.someValue}>
{renderWrapper ? <div className="differentMarkup"> : ''}
{itemData.someValue}<br>{itemData.anotherValue}
{renderWrapper ? </div> : ''}
</div>
)
},
render: function() {
// Prepare the variables you will need only once
let parsedUserItems = this.state.userItems.map(this.prepareItemData);
return (
<div>
<div className="hidden-xs">
{ parsedUserItems.map(item => this.renderUserItem(item, false)) }
</div>
<div className="visible-xs">
{ parsedUserItems.map(item => this.renderUserItem(item, true)) }
</div>
</div>
)
}
Original answer below:
If I understood correctly and the inner items have the same markup, you could extract the function passed into map to a separate method:
renderUserItem: function(item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
{someValue}<br>{anotherValue}
</div>
)
},
render: function() {
return (
<div>
<div className="hidden-xs">
{ this.state.userItems.map(this.renderUserItem) }
</div>
<div className="visible-xs">
{ this.state.userItems.map(this.renderUserItem) }
</div>
</div>
)
}

Categories

Resources