Reputation: 187
I have a table rows data from server containing images (some other data is removed for simplicity). When images clicked, a modal popup is shown to preview the loaded image to crop and change the image with the cropped one. Everything is work fine.
The problem is, the clicked image on the row should change after the modal submit button is clicked. But I found that the image on the last row is changed.
I know the problem comes from this line but I have no idea how to solve it :
handleSubmit = e => {
e.preventDefault();
console.log(this.state.croppedImageUrl);
this.imagetoCropt.src = this.state.croppedImageUrl;
};
This is the code :
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";
import { Modal } from "react-bootstrap";
import ReactCrop from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
import { my_ads } from "./component/AdsFunctions";
export default class Myads extends Component {
constructor() {
super();
this.state = {
myads : {},
modalShow: false,
setShow: false,
setClose: true,
previewImage: "/assets/loader.gif",
src: null,
crop: {
unit: "%",
width: 30,
aspect: 5 / 4
}
};
}
handleImageOnChange = e => {
if (e.target.files && e.target.files.length > 0) {
const reader = new FileReader();
reader.addEventListener("load", () =>
this.setState({
src: reader.result,
modalShow: true
})
);
reader.readAsDataURL(e.target.files[0]);
}
};
onImageLoaded = image => {
this.imageRef = image;
};
onCropComplete = crop => {
this.makeClientCrop(crop);
};
onCropChange = (crop, percentCrop) => {
this.setState({ crop });
};
async makeClientCrop(crop) {
if (this.imageRef && crop.width && crop.height) {
const croppedImageUrl = await this.getCroppedImg(
this.imageRef,
crop,
"newFile.jpeg"
);
this.setState({ croppedImageUrl });
}
}
getCroppedImg(image, crop, fileName) {
const canvas = document.createElement("canvas");
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
);
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
//reject(new Error('Canvas is empty'));
console.error("Canvas is empty");
return;
}
blob.name = fileName;
window.URL.revokeObjectURL(this.fileUrl);
this.fileUrl = window.URL.createObjectURL(blob);
resolve(this.fileUrl);
}, "image/jpeg");
});
}
//---- modal function ------------
handleShow = () => {
this.setState({
modalShow: true
});
};
handleClose = () => {
this.setState({
modalShow: false
});
};
handleImgClick = () => {
this.refs.fileInput.click();
};
handleClickSubmit = () => {
this.refs.btnSubmit.click();
this.setState({
modalShow: false
});
};
//--------- end modal function---
//======== PROBLEM HERE ======================
handleSubmit = e => {
e.preventDefault();
console.log(this.state.croppedImageUrl);
this.imagetoCropt.src = this.state.croppedImageUrl;
};
//=============================================
componentDidMount() {
// AXIOS call
my_ads().then(res => {
this.setState({
myads: res.myads,
});
});
}
render() {
const { crop, croppedImageUrl, src } = this.state;
const show = this.state.modalShow;
// My Ads List from AXIOS call
let myads = this.state.myads;
const RenderMyAds = Object.keys(myads).map((val, index) => (
<tr className="mt-3" key={index}>
<td>
<div className="float-left mr-4">
<div className="card mb-10">
<Link to="#">
<img
className="img-thumbnail img-responsive"
src={myads[val].image}
alt="img"
width={200}
onClick={this.handleImgClick}
ref={ref => (this.imagetoCropt = ref)} <<==== problem here?
/>
</Link>
</div>
</div>
</td>
</tr>
));
return (
<div>
<section>
<div className="container">
<div className="row">
<div className="col-lg-12">
<div className="card">
<div className="card-body">
<div className="table-responsive">
<table className="table table-bordered border-top mb-0">
<tbody>
{RenderMyAds}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<form
encType="multipart/form-data"
acceptCharset="utf-8"
onSubmit={this.handleSubmit}
>
<input
type="file"
className="d-none"
name="userfile"
ref="fileInput"
onChange={this.handleImageOnChange}
/>
<button type="submit" className="d-none" ref="btnSubmit">
Upload Image
</button>
</form>
<Modal size="lg" show={show} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>Image Preview</Modal.Title>
</Modal.Header>
<Modal.Body className="text-center"></Modal.Body>
<ReactCrop
src={src}
crop={crop}
onImageLoaded={this.onImageLoaded}
onComplete={this.onCropComplete}
onChange={this.onCropChange}
/>
<img className="d-none" alt="Crop" src={croppedImageUrl} />
<Modal.Footer>
<Button
variant="primary"
className="btn-block"
onClick={this.handleClickSubmit}
>
<i className="fa fa-image mr-2"></i> Upload Image
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
Upvotes: 1
Views: 209
Reputation: 24549
You are overwriting the same ref in your map. Consequentially, the last row is the last one to be mapped. You need to instead use an array of refs.
In your contructor, add:
this.imageRefs = [];
Then in your mapping:
const RenderMyAds = Object.keys(myads).map((val, index) => (
<tr className="mt-3" key={index}>
<td>
<div className="float-left mr-4">
<div className="card mb-10">
<Link to="#">
<img
className="img-thumbnail img-responsive"
src={myads[val].image}
alt="img"
width={200}
onClick={this.handleImgClick}
ref={ref => (this.imageRefs[index] = ref)}
/>
</Link>
</div>
</div>
</td>
</tr>
));
This will let you access the correct ref, based on the key assigned to the tr
.
Upvotes: 1