Yusnee
Yusnee

Reputation: 187

React for table rows to bind (this) click event handler

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

Answers (1)

Jeremy Harris
Jeremy Harris

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

Related Questions