Reputation: 147
I have a created a parent component PicturesFetch
that renders another (children) component, Picture
which renders a div with a picture inside. Now, to render Picture
, PicturesFetch
goes through an object (pics) and with a .map() function creates multiple divs with images inside. I want to be able to change the state of each element created separately. Here is what I tried so far, the tested binds were tried one by one, not all together:
PictureFetch.js
class PicturesFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
isSelected: true,
pictureClassName: "picture-div green",
pics : [/*Object witch contains names and src of the images*/]
};
this.handlePictureClick = this.handlePictureClick.bind(this); /*#1 tested bind*/
}
handlePictureClick (isSelected){
if (!isSelected) {
this.setState({
isSelected: true,
pictureClassName: "picture-div green"
})
} else {
this.setState({
isSelected: false,
pictureClassName: "picture-div none"
})
}
}
render() {
var changeClass = this.state.pictureClassName;
var handlePictureClick = this.handlePictureClick.bind(this); /*#2 tested bind*/
var pics = this.state.pics.map(function(pic, i){
if (/*something*/) {
return (
<div className="pic-div" key={i} >
<Picture
isSelected={isSelected}
pictureClassName={changeClass}
onClick={handlePictureClick.bind(this, isSelected)} /*#3 tested bind*/
/>
</div>
});
}
return (
<div className="Options-container">
<div className="container">{pics}</div>
</div>
);
}}
Picture.js
class Picture extends React.Component {
constructor(props) {
super(props);
this.state = {
isSelected: true,
pictureClassName: "picture-div green"
};
}
render() {
var pictureClassName = this.props.pictureClassName;
return (
<div onClick={this.props.onClick} className={pictureClassName}>
<img src={this.props.src} alt={this.props.name} />
<h5>{this.props.name}</h5>
</div>
)
}}
I simplified my code for the example. Now, by placing the bind in positions #1 and #2 changes all the images state together, while in #3 gives me the error Cannot read property 'setState' of undefined.
Which is the correct place to bind the onClick function so I can access each image state separately? Maybe my structure is flawed? Thank you in advance.
Upvotes: 6
Views: 6593
Reputation: 132
I think you should you use the first bind option.
Your isSelected boolean is shared by all you child picture, so when iselected is true, it is for all your picture component.
I'm made a working fiddle here
class PicturesFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: null,
pictureClassName: "picture-div green",
pics : [{name : '1'},{name : '2'},{name : '3'},{name : '4'}]
};
this.handlePictureClick = this.handlePictureClick.bind(this); /*#1 tested bind*/
}
handlePictureClick (itemSelected){
console.log(itemSelected)
this.setState({
selected: itemSelected,
pictureClassName: "picture-div green"
})
}
render() {
var changeClass = this.state.pictureClassName;
var selected = this.state.selected;
var click = this.handlePictureClick;
var pics = this.state.pics.map((pic, i) => {
var itemSelected = selected && selected.name === pic.name;
return (
<div className="pic-div" key={i} >
<Picture
isSelected={itemSelected}
pictureClassName={changeClass}
onClick={click}
pic={pic}
/>
</div>
);
});
return (
<div className="Options-container">
<div className="container">{pics}</div>
</div>
)
}}
class Picture extends React.Component {
constructor(props) {
super(props);
}
render() {
var pictureClassName = this.props.pictureClassName;
console.log(this.props.isSelected)
return (
<div onClick={ () => this.props.onClick(this.props.pic)} className={pictureClassName}>
<h5>{this.props.pic.name} {this.props.isSelected ? 'selected' : 'not selected'}</h5>
</div>
)
}}
I hope it's help you
Upvotes: 3
Reputation: 281626
Adding to the solution given by @dulwalanise the problem can be solved by bind the map function to the context. this.handlePictureClick
is undefined because here this
doesn't refer to the context of the component rather the function inside of map
use this
handlePictureClick (pic){
this.setState(pics : this.state.pics.map(function(picture){
if(picture.id === pic.id){
if(picture.isSelected){
picture.isSelected = false;
picture.pictureClassName = "picture-div none";
} else {
picture.isSelected = true;
picture.pictureClassName = "picture-div green";
}
}
return picture;
));
}
render() {
var changeClass = this.state.pictureClassName;
var pics = this.state.pics.map(function(pic, i) {
if (/*something*/) {
return (
<div className="pic-div" key={i} >
<Picture
isSelected={pic.isSelected}
pictureClassName={changeClass}
onClick={this.handlePictureClick.bind(this, pic)}
/>
</div>
}
}.bind(this));
return (
<div className="Options-container">
<div className="container">{pics}</div>
</div>
);
}
Upvotes: 3
Reputation: 1302
You can use either #1 or #3 to bind onClick function. Here in #3 you are missing this in handlePictureClick.
and for the second part accessing each image state separately. you will have to put the isSelected and pictureClassName key inside each pic object. and handlePictureClick can be modified as below.
handlePictureClick (pic){
this.setState(pics : this.state.pics.map(function(picture){
if(picture.id === pic.id){
if(picture.isSelected){
picture.isSelected = false;
picture.pictureClassName = "picture-div none";
} else {
picture.isSelected = true;
picture.pictureClassName = "picture-div green";
}
}
return picture;
));
}
render() {
var changeClass = this.state.pictureClassName;
var pics = this.state.pics.map(function(pic, i) {
if (/*something*/) {
return (
<div className="pic-div" key={i} >
<Picture
isSelected={pic.isSelected}
pictureClassName={changeClass}
onClick={this.handlePictureClick.bind(this, pic)}
/>
</div>
}
});
return (
<div className="Options-container">
<div className="container">{pics}</div>
</div>
);
}
Upvotes: 4