I have looked at this same issue but could not figure out where my error is.
My live search box works great but once you have removed your search query (word) the warning shows up in the react console:
Warning: flattenChildren(...): Encountered two children with the same
key, .$1. Child keys must be unique; when two children share a key,
only the first child will be used.
This is my entire jsx file:
var SearchBox = React.createClass({
doSearch:function(){
// remove .getDOMNode() for newer version of reat-rails 1.4.x
var query = (this.refs.searchInput.value).toLowerCase(); // this is the search text
this.props.doSearch(query);
},
render:function(){
return <input type="text" ref="searchInput" placeholder="Search title, country and city" value={this.props.query} onChange={this.doSearch}/>
}
});
var DisplayTable = React.createClass({
render:function(){
//making the cards to display
var cards=[];
var default_url = "/assets/profile_avatar/thumb/missing.png";
this.props.data.map(function(s) {
cards.push(
<a href={'/users/' + s.user.id + '/supports/' + s.id} key={s.id}>
<div className="card">
<div className="row">
<div className="small-5 large-6 columns">
<h5>{s.support_title}</h5>
</div>
<div className=" fi small-3 large-3 columns">
Reward: {s.reward_after_support}
</div>
<div className="small-4 large-3 columns talr">
{s.remote_support == 'on' ? 'Remote / Anywhere' : s.city + ', '+ s.country}
</div>
<hr />
<div className="cl">
<img className="profile-img" src={ s.profiles[0].avatar_file_name === null ? default_url : s.profiles[0].avatar_url}></img><span>{s.profiles[0].first_name}</span>
</div>
<div className="cr">
Applications: {s.interest_recieved} |
Posted: {moment(s.created_at).fromNow()}
</div>
</div>
</div>
</a>
)
});
//returning the card
return(
<div>
{cards}
</div>
);
}
});
var InstantBox = React.createClass({
doSearch:function(queryText){
//console.log(queryText)
//get query result
var queryResult=[];
this.props.data.map(function(s){
if(s.support_title.toLowerCase().indexOf(queryText)!=-1) {
queryResult.push(s);
}
if (s.city != null) {
if(s.city.toLowerCase().indexOf(queryText)!=-1) {
queryResult.push(s);
}
}
if (s.country != null) {
if(s.country.toLowerCase().indexOf(queryText)!=-1) {
queryResult.push(s);
}
}
});
this.setState({
query:queryText,
filteredData: queryResult
})
},
getInitialState:function(){
return{
query:'',
filteredData: this.props.data
}
},
render:function(){
return (
<div className="InstantBox">
<div className="search">
<SearchBox query={this.state.query} doSearch={this.doSearch}/>
</div>
<DisplayTable data={this.state.filteredData}/>
</div>
);
}
});
I really dont see an issue. Am I missing something?
The quirk in your code is that the indexOf()!=-1 only works if the passed string is not empty. So
'Milan'.indexOf('M') // = 0
'Milan'.indexOf('Q') // = -1
'Milan'.indexOf('') // = 0 !!
So if your search string is empty, the doSearch()will actually add all records with any city to the search results AND add the same record if it also has any country.
This last bit causes search results with multiple items with the same id. And react does not like that.
Also: if you have a record with city = "Paris", and country is "France", then you search query of "r" will lead to the same error. The letter "r" is in city -> record is added. Letter "r" is also in country -> same record will be added again.
Full solution also needs to ensure that each record is only added once:
if ( queryText == '' // if no search, then show all
|| s.support_title.toLowerCase().indexOf(queryText)!=-1
|| (s.city && s.city.toLowerCase().indexOf(queryText)!=-1)
|| (s.country && s.country.toLowerCase().indexOf(queryText)!=-1) ){
queryResult.push(s);
}
This ensures each record is added only once.
Sidenote: the way you fill your cards could be simplified, and has some unnecessary code.
You could also do:
cards = this.props.data.map(function(s) {
<a ...
});
Related
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>
);
}
}
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 ).
I'm trying to build a Jeopardy like game using React and Redux. I currently have an onClick event set to each li, but whenever I click on it, I get every Modal to pop up instead of the one that is attached to that li item. I have my code separated in different files but I believe these two files are the only ones I need to show:
const ModalView = React.createClass({
pass: function(){
console.log('pass clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
submit: function(){
console.log('submit clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let question = this.props.question
let category = this.props.category
let answer = this.props.answer
let val = this.props.value
return (
<div>
<div className="modal">
<p>{category}</p>
<p>{question}</p>
<p>{answer}</p>
<p>{val}</p>
<input
type="text"
placeholder="type in your answer">
</input>
<button onClick={this.submit}>Submit</button>
<button onClick={this.pass}>Pass</button>
</div>
</div>
)
}
})
and ValuesView
const ValuesView = React.createClass({
modalPopUp: function(value){
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let showClass = "show-content"
let hideClass = "hide-content"
if (this.props.modal){
showClass = "hide-content"
hideClass = "show-content"
}
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp} key={i}>$600</li>
</div>
}
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})
How would I go about only getting the corresponding Modal to display instead of every one? Thanks!!
If you just want to code real Modal I suggest you to use some already implemented component like https://github.com/reactjs/react-modal (I'm not saying is mandatory, nor even whit the example I suggest, could be other)
I think that store.dispatch({type:"MODAL_TOGGLE"}) in some way toggle modal prop between true and false. Then you just use that flag to toggle a class to show or hide content I guess (I would need to see you css).
The problem with this approach is (apart that is not the best way to do this for many reasons) that you are using the same class for every item in your clues array.
In some way you need to store which is the "modal" you want to show, and then in the render, just apply the show class to this item.
Maybe:
const ValuesView = React.createClass({
modalPopUp: function(index){
return function (event) {
store.dispatch({
type:"MODAL_TOGGLE",
payload: {
modalIndex: index // Save modalIndex prop
}
})
}
},
render: function(){
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp(i)} key={i}>$600</li>
</div>
}
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp(i)} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})
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>
)
}
I have a reactJS component that looks like this :
var LikeCon = React.createClass({
render(){
return this.renderLikeButton(this.props.like, this.props.likeCount)
},
renderLikeButton(like, likeCount){
var content;
var tmpLikeCount;
if(likeCount < 1){
tmpLikeCount = "";
}
else{
tmpLikeCount = likeCount;
}
if(like == 1){
content = <div className="likeButConAct"><div className="likeB"> </div><div className="likeCount">{tmpLikeCount}</div></div>
}
else{
content = <div className="likeButCon"><div className="likeB"> </div><div className="likeCount">{tmpLikeCount}</div></div>
}
return content;
}
});
Say that I want to hide the likeCount element if there is no likes. How do I do this as simple as possible? I donĀ“t want another component to render this.
If your variable is null or undefined then React simply won't render it. That means your conditional code can be as simple as:
var tmpLikeCount;
if(likeCount >= 1){
tmpLikeCount = likeCount;
}
But I think you can make your code even simpler using class sets:
http://facebook.github.io/react/docs/class-name-manipulation.html
var LikeCon = React.createClass({
render(){
var likeCountCmp;
var classes = React.addons.classSet({
likeButCon: true,
active: this.props.like
});
if(this.props.likeCount > 0) {
likeCountCmp = <div className="likeCount">{this.props.likeCount}</div>;
}
return (
<div className={classes}>
<div className="likeB"> </div>
{likeCountCmp}
</div>
)
}
});
A final variation that I think will work is to use an implicit function return:
var LikeCon = React.createClass({
render(){
var classes = React.addons.classSet({
likeButCon: true,
active: this.props.like
});
return (
<div className={classes}>
<div className="likeB"> </div>
{this.getLikeCountCmp()}
</div>
)
},
getLikeCountCmp: function() {
if(this.props.likeCount > 0) {
return <div className="likeCount">{this.props.likeCount}</div>;
}
}
});
if we don't specifically return anything from getLikeCountCmp, we end up with undefined, which React renders as nothing.
Note that I'm a bit confused with your like == 1 comparison - should that be true/false rather than a number? I've assumed this.props.like will be true or false in my examples. That means it'd be called with:
<LikeCon like={true|false} likeCount={5} />
If you like to put everything inline, you can do this:
renderLikeButton(like, likeCount){
return (<div className={like==1 ? "likeButConAct" : "likeButCon" }>
<div className="likeB"> </div>
{ likeCount > 0 ? <div className="likeCount">{likeCount}</div>: null }
</div>);
}
That way you wont be rendering .likeCount div if likeCount is 0.
jsFiddle: http://jsfiddle.net/715u9uvb/
what about using the className to hide the element?
something like :
var cssClasses = "likeButConAct ";
if ( likeCount < 1 ) {
cssClasses += "hidden";
}
...
return <div className=cssClasses><div ...
EDIT
var content;
var tmpLikeCount;
var likeCounterComponent;
if(likeCount > 0){
likeCounterComponent = <div className="likeCount">{likeCount}</div>
}
if(like == 1){
cssClasses = "likeButConAct"
}
else{
cssClasses = "likeButCon";
}
return (
<div className=cssClasses>
<div className="likeB"> </div>
{ likeCounterComponent }
</div>);
You can add the likeCounter only if there are likes. If there are likes the likeCounterComponent contains the JSX code to render the likes counter, otherwise is undefined and therefore nothing will be rendered.
I haven't tried to run the code, but I guess you got the idea to solve this problem. :D
Colin's answer looks good to me.. if your issue is with having aspects of rendering extracted to a separate function, you don't HAVE to do that. This works too:
return (
<div className={classes}>
<div className="likeB"> </div>
{this.props.likeCount > 0 && (
<div className="likeCount">{this.props.likeCount}</div>
)};
</div>
)
....
if (likeCount < 1) {
return "";
}
....