简体   繁体   中英

React - Unexpected behavior possibly caused by passing functions as props

I'm trying to build a list of thumbnail image components which when clicked will update the state of the parent. Eventually, I do get the desired behavior, but it takes several clicks and the effect is usually one-click behind the input. I'm speculating that this has something to do with how the functions are being passed as props, but am completely at a loss for what's going on.

EDIT: I think the issue is that the page renders before the setState changes the image state, which is why each click executes with the image that was passed via the previous click. I either need to fin a way to wait until the state updates to render, or do something in ComponentDidUpdate to rerender the page (which seems hackish but still a possibility).

This is the code I have so far:

var ImageSelector = React.createClass({

getInitialState: function(){
    return{
        imgState:  "<%= image_path('image1.jpg') %>"
    }
},

_changePicState: function(thumbnail){
    var newImage = thumbnail.props.imageLink
    this.setState({imgState: newImage})
},

_getThumbnails: function(){
    console.log('_getThumbnails')
    const thumbnailList = [
    {id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
    {id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
    {id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
    ]

    return thumbnailList.map((e) => {
        return (
            <ImageThumbnail key={e.id} imageLink={e.imageLink} propFunc={this._changePicState}/>
            )
    });
},

render: function() {
const thumbnails = this._getThumbnails()

return (
    <div>
    {thumbnails}
    <MyCanvasComponent ref="canvasComp" imageLink={this.state.imgState}/>
    </div>

)
}

});

var ImageThumbnail = React.createClass({
_runPropFunc: function(){
    this.props.propFunc(this)
},

render: function(){
    return (
        <img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
        )
}
})

EDIT: Including the myCanvasComponent code below.

var MyCanvasComponent = React.createClass({

getInitialState: function(){
    return {
        currentImage: this.props.imageLink
    }
},
componentDidUpdate: function(){
    this._draw()
},

_draw: function(){
    var draw = function(){
        ctx.drawImage(objectImg, 100, 100);
    }
    var can = this.refs.canvas;
    var ctx = can.getContext('2d');

    var objectImg = new Image();
    var imgPath = this.state.currentImage;
    objectImg.src = imgPath
    console.log('drawing ' + imgPath)

    objectImg.onload = function(){
        draw();
    }
},


componentWillReceiveProps: function(){
    this.setState({currentImage: this.props.imageLink});
},

componentDidMount: function(){
    console.log('canvas rendered')
    this._draw()

},

render: function() {
    return (
        <div>
        <canvas ref='canvas' width={867} height={600}/>
        </div>
    );
}
})

First off, I would drop the habit of prefixing your function names with underscores. It serves no purpose: it doesn't actually make them private functions. It's code mess, basically. That's probably a bit subjective, but I thought I'd offer that up anyway. :)

Secondly, in ImageThumbnail , I wouldn't pass this back to the parent, when all you really need is the imageLink . So just pass that.

Try the code below. I also renamed the prop to onClick so it's clearer what's going on. When making your own components that have event handlers, try to stick to conventional names ( onClick , onChange , onDeleteThumbnail ). It'll make your life easier!

在此处输入图片说明

Side note: If you have time, try to get on board with the ES2015 class way of doing things .

var ImageSelector = React.createClass({
  getInitialState: function () {
    return {
      imgState: "<%= image_path('image1.jpg') %>"
    }
  },

  changePicState: function (imageLink) {
    this.setState({ imgState: imageLink });
  },

  getThumbnails: function () {
    console.log('getThumbnails');
    const thumbnailList = [
      { id: 1, imageLink: "<%= image_path('image1.jpg') %>" },
      { id: 2, imageLink: "<%= image_path('image3.jpg') %>" },
      { id: 3, imageLink: "<%= image_path('image7.jpg') %>" },
    ];

    return thumbnailList.map((e) => {
      return (
        <ImageThumbnail key={e.id} imageLink={e.imageLink}
                        onClick={this.changePicState} />
      )
    });
  },

  render: function () {
    return (
      <div>
        <div>{this.getThumbnails()}</div>
        <div>{this.state.imgState}</div>
      </div>
    )
  }
});

var ImageThumbnail = React.createClass({
  runPropFunc: function () {
    this.props.onClick(this.props.imageLink);
  },

  render: function () {
    return (
      <img key={this.props.id} src={this.props.imageLink} className="thumbnail"
           onClick={this.runPropFunc} />
    )
  }
});

I still didn't get rid of the underscores, but it's on the to-do list. I feel like this component might be getting a bit bloated, but it ensures that the new canvas element gets drawn after the state change. Previously, the state change would be queued and the new component would be rendered (along with the draw function which used to be in the MyCanvasComponent) and the state would change after rendering, so everything lagged behind by one click.

Thank you once again for your help!

var ImageSelector = React.createClass({

getInitialState: function(){
    return{
        imgState:  "<%= image_path('image1.jpg') %>"
    }
},

_draw: function(){
    var draw = function(){
        ctx.drawImage(objectImg, 100, 100);
    }
    var can = this.refs.canvas;
    var ctx = can.getContext('2d');

    var objectImg = new Image();
    var imgPath = this.state.imgState;
    objectImg.src = imgPath

    objectImg.onload = function(){
        draw();
    }
},

componentDidUpdate: function(){
    this._draw()
},
componentDidMount: function(){
    this._draw()
},

_changePicState: function(imageLink){
    this.setState({imgState: imageLink})
},

_getThumbnails: function(){
    const thumbnailList = [
    {id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
    {id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
    {id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
    ]

    return thumbnailList.map((e) => {
        return (
            <ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
            )
    });
},

render: function() {
const thumbnails = this._getThumbnails()

return (
    <div>
    {thumbnails}
    <canvas ref='canvas' width={867} height={600}/>
    </div>

)
}

});

var ImageThumbnail = React.createClass({

_runPropFunc: function(){
    this.props.onClick(this.props.imageLink)

},

render: function(){
    return (
        <img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
        )
}
})

The problem lies in your MyCanvasComponent . You're setting the new state in the componentWillReceiveProps lifecycle method by using this.props but this.props references the old props, the new props are passed as a parameter to the componentWillReceiveProps function

By the way, you don't need to hold the current image in the MyCanvasComponent since this state is already managed by the ImageSelector component, passing down the current image from ImageSelector to MyCanvasComponent is sufficient in this case :

 var ImageSelector = React.createClass({ getInitialState: function(){ return{ imgState: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg" } }, _changePicState: function(imageLink){ this.setState({imgState: imageLink}) }, _getThumbnails: function(){ const thumbnailList = [ {id: 1, imageLink: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"}, {id: 2, imageLink: "https://s3.amazonaws.com/media-p.slid.es/uploads/jhabdas/images/969312/react-logo-1000-transparent.png"}, {id: 3, imageLink: "http://felknar.com/images/icon-react-7b609cd3.svg"}, ] return thumbnailList.map((e) => { return ( <ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/> ) }); }, render: function() { const thumbnails = this._getThumbnails() return ( <div> {thumbnails} <MyCanvasComponent imageLink={this.state.imgState}/> </div> ) } }); var ImageThumbnail = React.createClass({ _runPropFunc: function(){ this.props.onClick(this.props.imageLink) }, render: function(){ return ( <img key={this.props.id} width={50} height={50} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} /> ) } }) var MyCanvasComponent = React.createClass({ _draw: function(){ var draw = function(){ ctx.drawImage(objectImg, 100, 100); } var can = this.refs.canvas; var ctx = can.getContext('2d'); var objectImg = new Image(); var imgPath = this.props.imageLink; objectImg.src = imgPath objectImg.onload = function(){ draw(); } }, componentDidUpdate: function(){ this._draw() }, componentDidMount: function(){ this._draw() }, render: function() { return ( <canvas ref='canvas' width={867} height={600}/> ) } }) ReactDOM.render(<ImageSelector/>, document.getElementById('app')) 
 <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="app"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM