Cannot read property of undefined with array.map - javascript

I have a component which displays images on click of a button. These images when get displayed on the screen, I want to apply an onClick listener to them such that the image that I click on gets displayed on the whole screen.
Code:
class App extends React.Component{
constructor(props){
super(props);
this.state={
images:[],
pano:'',
name:'',
list:[]
}
this.loadImages=this.loadImages.bind(this);
this.loadOne=this.loadOne.bind(this);
}
loadImages(){
console.log("load");
var that=this;
$.ajax({
type:'GET',
url:'https://demo0813639.mockable.io/getPanos',
success:function(result){
var images=that.state.images;
for(var i=0;i<result.length;i++){
that.state.images.push({"pano":result[i].pano,"name":result[i].name});
}
that.setState({
images:images
})
}
})
}
loadOne(url){
console.log("hi")
}
render(){
var list=this.state.list;
list=this.state.images.map(function(result){
//console.log(result.name);
return(<div className="box">
<div className="label">{result.name}</div>
<img src={result.pano} className="image"/>
</div>
)
})
return(
<div>
<button onClick={this.loadImages}>Click</button>
<div onClick={this.loadOne(this,this.props.result.pano)}>{list}</div>
</div>
);
}
}
loadOne() is the function which gets called after clicking on an image. I get the error:
cannot read property pano of undefined
And if I do like this:
render(){
var list=this.state.list;
list=this.state.images.map(function(result){
return(<div className="box">
<div className="label">{result.name}</div>
<img src={result.pano} className="image" onClick={this.loadOne(this,this.props.result.pano)}/>
</div>
)
})
return(
<div>
<button onClick={this.loadImages}>Click</button>
<div >{list}</div>
</div>
);
}
then I get the error:
cannot read property loadOne of undefined.
So, how can I pass specific image url to my function?

Issue with second snippet is:
1- You forgot to bind the map callback method, use arrow function:
var list = this.state.list;
list = this.state.images.map((result) => { //here
.....
2- You missed the bind word here:
onClick = {this.loadOne.bind(this,result.pano)}
3- Instead of this.props.result.pano it should be result.pano.
Full code:
var list = this.state.list;
list = this.state.images.map((result) => {
return(
<div className="box">
<div className="label">{result.name}</div>
<img src={result.pano} className="image" onClick={this.loadOne.bind(this,result.pano)}/>
</div>
)
})
Working Code:
class App extends React.Component{
constructor(props){
super(props);
this.state={
images:[],
pano:'',
name:'',
list:[]
}
this.loadImages=this.loadImages.bind(this);
this.loadOne=this.loadOne.bind(this);
}
loadImages(){
var that=this;
$.ajax({
type:'GET',
url:'https://demo0813639.mockable.io/getPanos',
success:function(result){
var images=that.state.images;
for(var i=0;i<result.length;i++){
that.state.images.push({"pano":result[i].pano,"name":result[i].name});
}
that.setState({
images:images
})
}
})
}
loadOne(pano){
console.log('pano', pano);
}
render(){
var list = this.state.list;
list = this.state.images.map((result)=>{
return(
<div className="box">
<div className="label">{result.name}</div>
<img src={result.pano} className="image" onClick={this.loadOne.bind(this,result.pano)}/>
</div>
)
})
return(
<div>
<button onClick={this.loadImages}>Click</button>
<div >{list}</div>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('container'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='container'/>
Check this answer: Why is JavaScript bind() necessary?

You need to bind you click function to that image.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [],
pano: '',
name: '',
list: []
}
this.loadImages = this.loadImages.bind(this);
//The loadOne cant be bound to this here, it needs a
//bind for each image. Effectivly creating one version of loadOne per image.
}
loadImages() {
$.ajax({
type: 'GET',
url: 'https://demo0813639.mockable.io/getPanos',
success:(result) => {
var images = this.state.images;
for (var i = 0; i < result.length; i++) {
this.state.images.push({ url: result[i].pano, name: result[i].name });
}
this.setState({
images: images
})
}
})
}
loadOne(url, mouseEvent) {
console.log(url);
}
render() {
//This can get extracted to a custom component
var imagesList = this.state.images.map((image, i) => {
return (<div key={i} className="box" >
<div className="label">{image.name}</div>
{/*This is where the bind/curry should be */}
<img onClick={this.loadOne.bind(this, image.url)} src={image.url} className="image" />
</div>
)
});
return (
<div>
<button onClick={this.loadImages}>Click</button>
<div>{imagesList}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));
Your clickhandler (loadOne) will then be called with the result.pano and the event as second argument passed in by react.
http://jsbin.com/zokugitopa/1/edit?js,output

Related

Hi ! React issues i need assistance with event handlers

I'm trying to make a components props(sideBarInfo) details show up on the left column of a Page after clicking on a corresponding component(thumbnail) on the right column of the same Page.
Please note that all imports and exports are used in the main project(i removed them here).
I've also imported all components into the main (Page.js). Yet i keep getting a Type error for the onClick.
This is the first component - Thumbnail
class Thumbnail extends React.Component {
render(){
return (
<div className="Work" onClick={(e) => this.props.click(this.props.work)} >
<div className="image-container">
<img src={this.props.work.imageSrc} alt={this.props.work.imageSrc}/>
</div>
<div className="Work-information">
<p> {this.props.work.work}</p>
</div>
</div>
);
} }
This is the ThumbnailList
class ThumbnailList extends React.Component {
constructor(props){
super(props);
this.state= {
works: [
{
id: 0,
work: 'Work 1',
imageSrc: W1,
view: '#',
selected: false
},
{
id: 1,
work: 'Work 2',
imageSrc: W2,
view: '#',
selected: false
},
]
}
}
handleCardClick = (id,card) => {
console.log(id);
let works= [...this.state.works];
works[id].selected = works[id].selected ? false : true ;
works.forEach(work=>{
if(work.id !== id){
work.selected = false;
}
});
this.setState({
works
})
}
makeWorks = (works) => {
return works.map(work => {
return <Thumbnail work={work} click={(e => this.handleCardClick(work.id,e ))} key={work.id} />
})
}
render(){
return(
<div>
<div className="scrolling-wrapper-flexbox">
{this.makeWorks(this.state.works)}
</div>
</div>
);
}
}
This is the sidebarInfo
function SidebarInfo(props) {
return (
<img width="370" height="370" src= {props.imageSrc} />
<p> {props.work} </p>
);}
This is the problematic Page - the boldened keeps giving a Type error(cannot read property 'selected' of undefined.)
class Page extends React.Component {
render() {
return (
<span>
<div className="column left">
<div className="">
**{this.props.work.selected && <SidebarInfo imageSrc={this.props.imageSrc} /> }**
</div>
</div>
<div className="column right" >
<div>
<ThumbnailList />
</div>
</div>
</span>
)
}
}
You may need to add a check in this.props.work.selected like this this.props.work && this.props.work.selected
To ensure that this.props.work is defined before checking on this.props.work.selected
I was able to answer this for anyone interested.
there were some foundational errors in my file arrangement which have been corrected.
After refactoring, i was able to achieve what i wanted using 3 classes/functions in; App.js, Thumbnail.js and SideBarInfo.js
see one working solution with styling on this sandbox: https://codesandbox.io/s/modern-sky-y9d3c
See Solution below;
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
works: [
{
id: 0,
work: "Work 1",
imageSrc:
"https://images.unsplash.com/photo-1584608168573-b6eec7a04fd7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
view: "#",
selected: false
},
{
id: 1,
work: "Work 2",
imageSrc:
"https://images.unsplash.com/photo-1581665269479-57504728e479?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
view: "#",
selected: false
}
]
};
this.handleCardClick = this.handleCardClick.bind(this);
this.makeWorks = this.makeWorks.bind(this);
this.showWorks = this.showWorks.bind(this);
}
handleCardClick = (id, Thumbnail) => {
console.log(id);
let works = [...this.state.works];
works[id].selected = works[id].selected ? false : true;
works.forEach((work) => {
if (work.id !== id) {
work.selected = false;
}
});
this.setState({
works
});
};
makeWorks = (works) => {
return works.map((work) => {
return (
<Thumbnail
work={work}
click={(e) => this.handleCardClick(work.id, e)}
key={work.id}
/>
);
});
};
showWorks = (works) => {
let i = 0;
var w = [];
while (i < works.length) {
if (works[i].selected) {
w = works[i];
}
i++;
}
return ( <SidebarInfo imageSrc={w.imageSrc} work={w.work} /> );
};
render() {
return (
<div className="App">
<span>
<div> { this.showWorks(this.state.works)} </div>
<div className="">
<div className="scrolling-wrapper-flexbox">
{this.makeWorks(this.state.works)}
</div>
</div>
</span>
</div>
);
}
}
class Thumbnail extends React.Component {
render() {
return (
<div>
<div className="column left">
{/* this.props.work.selected && <SidebarInfo work={this.props.work.work} imageSrc={this.props.work.imageSrc}/> */}
</div>
<div className="column right">
<div className="Work" onClick={(e) => this.props.click(this.props.work)}>
<div className="image-container">
<img src={this.props.work.imageSrc} alt={this.props.work.imageSrc} />
</div>
<div className="Work-information">
<p> {this.props.work.work}</p>
</div>
</div>
</div>
</div>
);
}
}
export default Thumbnail;
function SidebarInfo(props) {
return (
<div className="one">
<div className="Work">
<h1> NAME </h1>
<div className="image-container">
<img
width="370"
height="370"
src={props.imageSrc}
alt={props.imageSrc}
/>
</div>
<p> {props.work} </p>
<p> {props.view} </p>
</div>
</div>
);
}
export default SidebarInfo;

Why TypeError for onclick function? - ReactJS

I am developing an app to fetch posts from a API and load posts.Everything is working fine except gotopost function.After rendering this error comes TypeError: Cannot read property 'gotopost' of undefined
But don't know why this is happening.Though i bind that function and declared that function above the render() function
below is the code
import React, { Component } from 'react';
import './App.css';
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
posts: [],
singleLoaded: false,
singlepost: []
}
this.fetchposts = this.fetchposts.bind(this);
}
fetchposts(){
fetch("http://smashingdevs.com/wp-json/wp/v2/posts/")
.then((response) => {
return response.json();
})
.then((posts) => {
this.setState({
isLoaded: true,
posts: posts
});
});
}
render() {
let container;
if(this.state.isLoaded){
container = <Allposts posts={this.state.posts}/>;
}else if(this.state.singleLoaded){
container = <Singleposts />;
}
return (
<div>
<h2>Hello there</h2>
<button onClick={this.fetchposts}>
fetch posts
</button>
{container}
</div>
);
}
}
class Allposts extends React.Component{
constructor(props){
super(props);
}
gotopost(){}
render(){
return (<div className="row mt-5">
{
this.props.posts.map(function(post){
return <div className="col-md-6 mb-2" key={post.id}>
<div className="card" >
// Problem is here on onClick
Go somewhere
</div>
</div>
})
}
</div>
)
}
}
class Singleposts extends React.Component{
}
export default Todos;
Use arrow function:
this.props.posts.map((post) => {
this will not be what you expect inside the function given to map. You can bind the function to this, or you can use an arrow function to use the surrounding lexical scope, and this will be what you want.
class Allposts extends React.Component {
gotopost() {}
render() {
return (
<div className="row mt-5">
{this.props.posts.map(post => {
return (
<div className="col-md-6 mb-2" key={post.id}>
<div className="card">
<a
href={post.link}
onClick={this.gotopost.bind(this)}
className="btn btn-primary"
>
Go somewhere
</a>
</div>
</div>
);
})}
</div>
);
}
}
This is common error of JS. When you go to internal function, var this in this scope is no longer outside class anymore. The workaround is assigning this to another variable at the top of method.
render(){
const self = this;
return (<div className="row mt-5">
{
this.props.posts.map(function(post){
return <div className="col-md-6 mb-2" key={post.id}>
<div className="card" >
// use self instead this
Go somewhere
</div>
</div>
})
}
</div>
)
}

this.someFunction is not a function

After having read about the bind requirement for methods to be bound to a React ES6 class, I am still having some difficulty with this example:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = { products: [] };
this.updateState = this.updateState.bind(this);
}
componentDidMount() {
this.updateState();
}
handleProductUpvote(productId) {
Data.forEach((item) => {
if (item.id === productId) {
item.votes = item.votes + 1;
return;
}
});
this.updateState();
}
updateState() {
const products = Data.sort((a,b) => {
return b.votes - a.votes;
});
this.setState({ products });
}
render() {
const products = this.state.products.map((product) => {
return (
<Product
key={'product-' + product.id}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitter_avatar_url={product.submitter_avatar_url}
product_image_url={product.product_image_url}
onVote={this.handleProductUpvote}
/>
);
});
return (
<div className='ui items'>
{products}
</div>
);
}
}
class Product extends React.Component {
constructor() {
super();
this.handleUpvote = this.handleUpvote.bind(this);
}
handleUpvote() {
this.props.onVote(this.props.id);
}
render() {
return (
<div className='item'>
<div className='image'>
<img src={this.props.product_image_url} />
</div>
<div className='middle aligned content'>
<div className='header'>
<a onClick={this.handleUpvote}>
<i className='large caret up icon'></i>
</a>
{this.props.votes}
</div>
<div className='description'>
<a href={this.props.url}>
{this.props.title}
</a>
</div>
<div className='extra'>
<span>Submitted by:</span>
<img
className='ui avatar image'
src={this.props.submitter_avatar_url}
/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<ProductList />,
document.getElementById('content')
);
This returns
Uncaught TypeError: this.updateState is not a function(...) at handleProductUpvote
Is the initialized binding not sufficient in this case?
Whenever you see this issue, you don't want to be adding the bind to the method that it's trying to call right then, but the method that you are inside of when the "this.xxx not defined" issue occurs.
Currently, it's getting the function handleProductUpvote just fine - but it's calling it in the wrong object context. So you need to do the same thing as you did with updateState in the constructor, but with that function. Though I have limited react knowledge I believe it's common to do that for every function that's used as an event listener or callback.

how to properly iterate through array of post objects retrieved through Wp Rest Api in react js

hello :) i am working on wp rest api and react js and i successfully retrieved data from wp rest api and displayed it, but its not the proper way to display data in react. console is showing the error message of ' Each child in an array or iterator should have a unique "key" prop ' . I read the document in react regarding the issue but didn't understood . Here is what i have written so far. Any help would be great thank you
class Home extends React.Component{
componentDidMount(){
const API_URL = 'http://localhost/wordpress/wp-json/wp/v2/posts/';
this.serverRequest = $.get(API_URL, function (result) {
this.setState({
result:result,
loaded:true
});
}.bind(this));
}
constructor(props) {
super(props);
this.state ={result:null,loaded:false};
autoBind(this);
}
render(){
if (this.state.loaded) {
return(
<div>
{this.state.result && <Portfolio data = {this.state.result}/>}
</div>
)
}
return(
<div>loading...
</div>
)
}
}
export default Home;
and the Portfolio component to which prop data is passed renders the data like this which is not the proper way
class Portfolio extends React.Component{
constructor(props) {
super(props);
autoBind(this);
}
render(){
var contents=[];
for (var i = 0; i < this.props.data.length; i++) {
if (this.props.data[i].categories[0] == 5) {
var productImage ={
backgroundImage:'url('+ this.props.data[i].featured_image + ')',
backgroundSize: '100% 100%'
}
contents.push(
<div id="portfolio-product-item" style ={productImage} >
<div id ="portfolio-product-item-details" >
<h3>{this.props.data[i].slug}</h3>
<div dangerouslySetInnerHTML={{__html: this.props.data[i].content.rendered}} />
</div>
</div>
);
}
}
return(
<section className="portfolio">
{contents}
</section>
)
}
}
export default Portfolio;
I haven't went over all of your code, just relating to the "key" error you got.
The issue is here -
contents.push(
<div id="portfolio-product-item" style ={productImage} >
<div id ="portfolio-product-item-details" >
<h3>{this.props.data[i].slug}</h3>
<div dangerouslySetInnerHTML={{__html: this.props.data[i].content.rendered}} />
</div>
</div>
);
The father div with the id of portfolio-product-item should also have an attribute key so the proper way to write it is -
contents.push(
<div key={i} id="portfolio-product-item" style ={productImage} >
<div id ="portfolio-product-item-details" >
<h3>{this.props.data[i].slug}</h3>
<div dangerouslySetInnerHTML={{__html: this.props.data[i].content.rendered}} />
</div>
</div>
);
Have a look at - https://facebook.github.io/react/docs/multiple-components.html#dynamic-children for further info.
To make your Portfolio component cleaner.
I suggest, split the logic and create a separate method from the main render.
class Portfolio extends React.Component{
constructor(props) {
super(props);
autoBind(this);
}
renderPortfolioOption(index, option) {
const { data } = this.props;
if (option.categories[0] == 5) {
var productImage ={
backgroundImage:'url('+ option.featured_image + ')',
backgroundSize: '100% 100%'
}
return(
<div key={index} id="portfolio-product-item" style ={productImage} >
<div id ="portfolio-product-item-details" >
<h3>{option.slug}</h3>
<div dangerouslySetInnerHTML={{__html: option.content.rendered}} />
</div>
</div>
);
}
}
render() {
const { data } = this.props;
if (data.length) return null;
return(
<section className="portfolio">
{ data.map((index, option) => { return this.renderPortfolioOption(index, option); }) }
</section>
)
}
}
export default Portfolio;

How to call setState function onClick ES6 way

class BlogPost extends React.Component{
//getInitialState
constructor(){
super();
this.onLike = this.onLike.bind(this);
this.state = {
like :0
}
}
onLike(){
this.setState({
like: this.state.like++
});
}
render(){
var postListItem = this.props.postList.map(function(post){
return <li> {post} </li>
});
return (
<div className="blogPost">
<h2>Posts</h2>
<ul>{postListItem}</ul>
<button onClick={(e) => {this.onLike}}>{this.state.like}</button>
</div>
);
}
}
Nothing happens on clicking Button.
There is no function on button and index.js has added Empty Function to it. IDK why?please explain
In this case you need remove arrow function ((e) => { }), it is not necessary here, because you bind this in constructor., also inside onLike change this.state.like++ to this.state.like + 1 because you can't mutate state
<button onClick={ this.onLike }>{this.state.like}</button>
class BlogPost extends React.Component{
constructor(){
super();
this.onLike = this.onLike.bind(this);
this.state = {
like: 0
}
}
onLike(){
this.setState({
like: this.state.like + 1
});
}
render(){
var postListItem = this.props.postList.map(function(post){
return <li> {post} </li>
});
return (
<div className="blogPost">
<h2>Posts</h2>
<ul>{ postListItem }</ul>
<button onClick={ this.onLike }>{this.state.like}</button>
</div>
);
}
}
ReactDOM.render(<BlogPost postList={ [] } />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
I think you're not invoking the onLike function in the annonymous function you're passing to onClick.
Try this:
<button onClick={(e) => {this.onLike()}}>{this.state.like}</button>
Note the () after onLike.

Categories

Resources