I have this:
var Astronomy = React.createClass({
getDefaultProps: function() {
return {meteo : JSON.parse(localStorage.getItem('meteo')).data};
},
render: function() {
return (
<div className="temps">
{this.props.meteo.weather.map(function(d, i) {return
<div className="waqt">
<div className="temps">
<div className="raise">
<div className="sunraise"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["sunrise"]}</i></div>
<div className="sunset"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["sunset"]}</i></div>
</div>
<div className="set">
<div className="moonraise"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["moonrise"]}</i></div>
<div className="moonset"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["moonset"]}</i></div>
</div>
</div>
</div>
}
)}
</div>
);
},
componentDidMount: function() {
return console.log(this.props.meteo.weather[0]["astronomy"][0]["sunrise"]);
},
});
But I get an empty result ! even the console gives what I expect 06:19 AM, and debugging it using chrome extension, I see that the array stayed as it is like in the screenshot:
JavaScript will insert a semicolon after return if it is followed by a line break. I.e.
function foo() {
return
42
}
is the same as
function foo() {
return;
42
}
i.e. the last line will never be evaluated and undefined will be returned.
The return value always has to be or start at the same line as the return statement:
return (
<div>...</div>
);
Also there is no need to access the data as this.props.meteo.weather[i]. That value is already passed to the callback as d, so you can just do d.astronomy[0].sunrise. Learn more about .map in the MDN documentation.
var Astronomy = React.createClass({
getDefaultProps: function() {
return {meteo : JSON.parse(localStorage.getItem('meteo')).data};
},
render: function() {
return (
<div className="temps">
{this.props.meteo.weather.map(function(d, i) {
return <div className="waqt">
<div className="temps">
<div className="raise">
<div className="sunraise"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["sunrise"]}</i></div>
<div className="sunset"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["sunset"]}</i></div>
</div>
<div className="set">
<div className="moonraise"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["moonrise"]}</i></div>
<div className="moonset"><i className="riz">{this.props.meteo.weather[i]["astronomy"][0]["moonset"]}</i></div>
</div>
</div>
</div>
},this )}
</div>
);
},
componentDidMount: function() {
return console.log(this.props.meteo.weather[0]["astronomy"][0]["sunrise"]);
},
});
this has change in the map function,your can appoint it by the second argument,or use ()=> ES6 arrow function.
Related
My console says that all keys are not unique, and that's why my removeItem function doesn't work. I'm trying to make a to-do list with React.js, and now I'm adding remove button. Can you help me? Here's the code:
var TodoItems = React.createClass({ //This is the removeItem function, that takes the key as a parameter and compares i.key to parameter.
removeItem: function(key){
var itemArray = this.props.entries;
for (var i = 0; i < itemArray.length; i++)
if (itemArray[i.key] === key) {
itemArray.splice(i, 1);
break;
}
},
render: function() {
var todoEntries = this.props.entries;
var _removeItem = this.removeItem;
function createTasks(item) {
return (
<div>
<li key={item.key}>{item.text}</li>
<button onClick = {_removeItem(item.key)} className= "remove"> Remove </button>
</div>
);
}
var listItems = todoEntries.map(createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
});
var TodoList = React.createClass({
getInitialState: function() {
return {
items: []
};
},
addItem: function(e) {
var itemArray = this.state.items;
//Here I create the key:
itemArray.push(
{
text: this._inputElement.value,
key: Math.random().toString(36).substring(7)
}
);
this.setState({
items: itemArray
});
this._inputElement.value = "";
e.preventDefault();
},
render: function() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit = {this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="enter task">
</input>
<button type="submit">add</button>
</form>
</div>
<TodoItems entries={this.state.items}/>
</div>
);
}
});
The problem lies within your createTasks function.
function createTasks(item) {
return (
<div>
<li key={item.key}>{item.text}</li>
<button onClick = {_removeItem(item.key)} className= "remove"> Remove </button>
</div>
);
}
You're return an array of divs to populate an unordered list. Firstly, you should be populating any ul element with li, and any content within that li element.
The reason reactjs is giving you can error is because you're adding your key to a child of the element you're returning instead of the root node, in this case a div.
Also, your removeItem function isn't working because it looks like it's being invoked when you're building each task, I've edited my answer to resolve this.
The following should work without issue:
function createTasks(item) {
return (
<li key={item.key}>
{item.text}
<button onClick = {this.removeItem.bind(this, item.key)} className= "remove"> Remove </button>
</li>
);
}
Edited: I misread a portion of the question, and have edited my answer.
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 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 ...
});
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 "";
}
....