Yuri Visovsiouk
Yuri Visovsiouk

Reputation: 147

Binding onClick for each Component in a .map() function

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

Answers (3)

nlarche
nlarche

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

Shubham Khatri
Shubham Khatri

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

duwalanise
duwalanise

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

Related Questions