I am learning React by trying to create a simple Tweetbox "uploading" multiple images using the browser HTML5 File API.
See screenshot below:
As it recommends in the official documentation I am using the inbuilt map function to loop over the uploaded image urls in an array.
I am enclosing this in a variable so that it can be reused in the JSX below within the wrapper div which I only want to show if an image has been uploaded.
The problem is that due to some sort of scope problem it can't "see" or "access" my _removePhoto function and click handler in this code snippet:
_removePhoto(event) {
let array = this.state.imagePreviewUrls;
let index = array.indexOf(event.target.result)
array.splice(index, 1);
this.setState({imagePreviewUrls: array });
// this.setState( {imagePreviewUrls: false} );
}
render() {
let {imagePreviewUrls} = this.state;
let {thereIsImage} = this.state;
// var {thereIsImage} = this.state;
// onClick={this._removePhoto}
var imageList = imagePreviewUrls.map(function(value, index) {
return (
<figure className="ma0 relative flex items-center justify-center">
<button onClick={this._removePhoto} className="button-reset pointer dim bn bg-black h2 w2 br-100 white flex items-center justify-center absolute absolute--fill-l center"><i className="material-icons f5">close</i>
</button>
<img key={index} src={value} className="h3 w3" />
</figure>
)
})
This gives the error:
Uncaught TypeError: Cannot read property '_removePhoto' of undefined
at bundle.js:19085
at Array.map (<anonymous>)
(The _removePhoto logic works if I don't enclose the map function in the imageList variable in this way).
Also if I remove the onClick event handler, it shows the image preview, but there is another error in the console saying that I need to include "index". However, I have already included this as per the documentation's instructions.
Here is my full code below:
import React from 'react'
import ReactDOM from 'react-dom'
// This library didn't help me solve the problem
// const classNames = require('classnames');
class TweetBox extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
files: [],
imagePreviewUrls: [],
thereIsImage: false
};
this._handleTextChange = this._handleTextChange.bind(this);
this._triggerFileDialogue = this._triggerFileDialogue.bind(this);
this._handlePhotoUpload = this._handlePhotoUpload.bind(this);
this._removePhoto = this._removePhoto.bind(this);
}
_removePhoto(event) {
let array = this.state.imagePreviewUrls;
let index = array.indexOf(event.target.result)
array.splice(index, 1);
this.setState({imagePreviewUrls: array });
// this.setState( {imagePreviewUrls: false} );
}
render() {
let {imagePreviewUrls} = this.state;
let {thereIsImage} = this.state;
// var {thereIsImage} = this.state;
// onClick={this._removePhoto}
var imageList = imagePreviewUrls.map(function(value, index) {
return (
<figure className="ma0 relative flex items-center justify-center">
<button onClick={this._removePhoto} className="button-reset pointer dim bn bg-black h2 w2 br-100 white flex items-center justify-center absolute absolute--fill-l center"><i className="material-icons f5">close</i>
</button>
<img key={index} src={value} className="h3 w3" />
</figure>
)
})
return (
<div>
{/* TITLE SECTION */}
<div className="pv2 tc bb b--black-10">
<h1 className="ma0 f5 normal">Create New Report</h1>
</div>
<div className="bg-near-white pa3">
<textarea onChange={this._handleTextChange} placeholder="Write your report here" name="tweet" rows="4" className="w-100 br2 ba b--black-10 pa2"></textarea>
{ imagePreviewUrls.length > 0 &&
<div className="bg-black-10 pa2 flex">
{imageList}
</div>
}
<input className="hide" ref={ (input) => { this.fileInput = input; } } onChange={ this._handlePhotoUpload } type="file"></input>
<div className="mt3 flex justify-between">
<button onClick={ this._triggerFileDialogue } className="button-reset flex items-center br2 bn bg-transparent blue hover-bg-black-10 pointer">
<i className="material-icons f3">photo_camera</i>
</button>
<div className="flex items-center">
<button disabled={ this.state.text.length === 0 } className="button-reset bg-blue bn white f6 fw5 pv2 ph3 br2 dim">Message</button>
</div>
</div>
{/* End "near-white" subcontainer */}
</div>
{/* End "b--black-10" parent container */}
</div>
);
} // End Render
_handleTextChange(event) {
this.setState( { text: event.target.value } );
// For debugging:
// console.log( { text: event.target.value })
}
_triggerFileDialogue(event) {
this.fileInput.click();
}
_handlePhotoUpload(event) {
// var self = this;
event.preventDefault();
let files = event.target.files;
for (let i = 0; i < files.length; i++) {
let reader = new FileReader();
reader.onloadend = (evt) => {
var imageString = evt.target.result;
let updatedImages = this.state.imagePreviewUrls.concat(imageString);
this.setState({
imagePreviewUrls: updatedImages,
files: files[i],
thereIsImage: true
});
}
reader.readAsDataURL(files[i]);
console.log(files[i]);
}
}
} // End Master React Class
ReactDOM.render( <TweetBox />, document.getElementById("app") );
Many thanks in advance, I've googled lots of things on Stack Overflow but can't find a solution to this! The code above should be reproducible.