user3209048 user3209048 - 1 month ago 14
Javascript Question

reactjs render array of objects into a list gives an error

so i create an object from a fetched json file and in showResult() function i add an object based on some conditions to an the array finalArray and i pass this array to the component Card and this should render a list, but it gives me an error items.map is not a function , but also if i change
finalArray: this.state.finalArray.push(new user(price, location, image, name, score))
to
finalArray: this.state.finalArray.concat(new user(price, location, image, name, score))

then it works but it only then shows the last object only showing only 1 list item which is not what i want, can someone help with pointing out the error or how to do this correctly since i am new to react and javascript

import React from 'react';
import Card from './Card.js';

export default class Slider extends React.Component {

constructor() {
super()
this.state = {
imgArray: [ "/img/work1.jpg", "/img/work2.jpg", "/img/work3.jpg"],
imgNo: 0,
url: "https://www.deskbookers.com/nl-nl/sajax.json?q=Amsterdam&type=-&people=any&favorite=0&pid=&sw=52.293753%2C4.634942&ne=52.455562%2C5.162286&ids=17201%2C19640%2C13692%2C13691%2C12136%2C17938%2C15292%2C14886%2C14885%2C14884%2C14883%2C15730%2C15353%2C15351%2C15330%2C15080%2C17290%2C15454%2C15451%2C15379",
current: "/img/work1.jpg",
search: '',
resultObject: null,
finalArray: [],
headlines: ["Pink Floyd Office", "Led Zeppelin Mania", "Central Perk Friends"],
headline : "Pink Floyd Office"
};
}

componentDidMount(){
this.serverRequest = $.get(this.state.url, function(result){
var info = result;
console.log(info);
this.setState({
resultObject:info
})
}.bind(this));
}

nextImg(){
if(this.state.imgNo < 2 && this.state.imgNo >=0 ){
this.setState({
imgNo : ++this.state.imgNo ,
current: this.state.imgArray[this.state.imgNo],
headline: this.state.headlines[this.state.imgNo]
})
}
}

prevImg(){
if(this.state.imgNo >= 1 && this.state.imgNo < 3 ){
this.setState({
imgNo : --this.state.imgNo,
current: this.state.imgArray[this.state.imgNo],
headline: this.state.headlines[this.state.imgNo]
})
}
}

searchQuery(e){
this.setState({
search: e.target.value
})
}

showResult(){

for(var i=0 ; i<this.state.resultObject.rows.length; i++){
if(this.state.search.toLowerCase() == this.state.resultObject.rows[i].location_city.toLowerCase()){
var price = this.state.resultObject.rows[i].day_price;
var location=(this.state.resultObject.rows[i].address[0]+", "+this.state.resultObject.rows[i].address[1]+", "+this.state.resultObject.rows[i].address[2]);
var image=this.state.resultObject.rows[i].image_urls2[0];
var name=this.state.resultObject.rows[i].location_name;
var score=this.state.resultObject.rows[i].location_rating;
if( price!=null && location!=null && image!=null && name!=null && score !=null){
function user(price, location, image, name, score){
this.price = price;
this.location = location;
this.image = image;
this.name = name;
this.score = score;
}
this.setState({
finalArray: this.state.finalArray.push(new user(price, location, image, name, score))
})
}
$(".card-list").show();
$('html,body').animate({
scrollTop: $(".card-list").offset().top},
'slow');
}
else{
$(".alert-box, .cancel").animate( { "opacity": "show", bottom:"0"} , 1250 );
$(".alert-box, .cancel").animate( { "opacity": "hide", bottom:"0"} , 3750 );
this.setState({
search: ""
})
$(".card-list").hide();
break;
}
}

}

render(){

return(
<div>
<div class="slider ">
<div class="img-container">
<img src={this.state.current} class="main-img" />
<div class="headline"><span>{this.state.headline}</span></div>
</div>
<img src="/img/slider-left.png" class="slider-arrow" onClick={this.prevImg.bind(this)} />
<img src="/img/slider-right.png" class="slider-arrow slider-right" onClick={this.nextImg.bind(this)} />
<div class="search-container">
<img src="/img/cancel.png" class="cancel hide"/>
<span class="alert-box hide">No offices available in this city, please try another one!</span>
<input onChange={this.searchQuery.bind(this)} value={this.state.search} type="text" name="search" placeholder="City name..." class="search-bar" />
<button disabled={!this.state.search} onClick={this.showResult.bind(this)} class="search-button">Sit me!</button>
</div>
</div>
<Card finalArray={this.state.finalArray}></Card>
</div>
);
}
}


import React from 'react';

export default class Card extends React.Component {


render(){
var items = this.props.finalArray;
var itemslist = items.map(function(item,index){
return(
<li key={index} class="card">
<img src={item.image} class="card-img" />
<div>
<div class="card-info">
<p class="workplace-name">{item.name}</p>
<span class="score">{item.score}</span>
<p class="location">{item.location}</p>
</div>
<div class="card-footer">
<p class="price">{item.price} &euro;</p>
</div>
</div>
</li>
);})
return(
<ul class="card-list">
{ itemslist }
</ul>

);

}
}

Answer

The .push method returns the new length of the array. So when you do

this.setState({
    finalArray: this.state.finalArray.push(...)
});

you are changing the value of this.state.finalArray from an array to a number. Of course numbers don't have a .map method.

If you want to add a new element to the array and create a new array, you can use .concat instead:

this.setState({
    finalArray: this.state.finalArray.concat(...)
});

Overall your code appears to be more complicated than it has to be. E.g. the user function is unnecessary, just create the object directly. The null checks might also be unnecessary.

I'm not exactly sure how you expect your code to work, but to me it looks like the showResult results method should rather look like this:

showResults() {
  var search = this.state.search.toLowerCase();
  var finalArray = this.state.resultObject.rows
    .filter(row => search == row.location_city.toLowerCase())
    .map(row => ({
      price: row.day_price,
      location: rows.address.slice(0,3).join(', '),
      image: row.image_urls2[0],
      name:  row.location_name,
      score: row.location_rating,
    }))
    .filter(user => user.price != null &&
      user.image != null &&
      user.name != null &&
      user.score != null
    );

  this.setState(
    {
      finalArray,
      search: finalArray.length > 0 ? this.state.search : '',
    },
    () => {
      // This is executed after the component updated
      if (finalArray.length > 0) {
        $(".card-list").show();
        $('html,body').animate({
          scrollTop: $(".card-list").offset().top
        }, 'slow');
      } else {
        $(".alert-box, .cancel").animate( { "opacity": "show", bottom:"0"} , 1250 );
        $(".alert-box, .cancel").animate( { "opacity": "hide", bottom:"0"} , 3750 );
        $(".card-list").hide();
      }
    }
  );
} 

That is, create your data first, an array of objects and update the components state. After the update, check whether there are results or not show or hide the list based on that result. Note that manually changing the style of components is not something you'd usually do with React.