# 7-2. Thành phần UploadPhoto

![tải ảnh lên](/files/2lrjYwDfMK2ap3QBCw7j)

1. Chức năng của thành phần `UploadPhoto`
2. Mã thành phần
3. Tương tác với hợp đồng
4. Cập nhật dữ liệu vào cửa hàng: hàm `updateFeed`

## 1) Chức năng của thành phần `UploadPhoto` <a href="#id-1-uploadphoto-component-s-role" id="id-1-uploadphoto-component-s-role"></a>

Thành phần `UploadPhoto` xử lý yêu cầu tải ảnh lên blockchain Klaytn. Quy trình như sau:

1. Gọi phương pháp `uploadPhoto` của hợp đồng thông minh bằng cách gửi một giao dịch. Trong phương pháp hợp đồng `uploadPhoto`, một token ERC-721 mới được tạo.
2. Sau khi gửi một giao dịch, hãy cho thấy tiến trình cùng vòng đời giao dịch bằng thành phần `Toast`.
3. Khi giao dịch tiến vào một khối, hãy cập nhật `PhotoData` mới trong cửa hàng redux cục bộ.

**Limiting content size**\
Kích thước tối đa của một giao dịch là `32KB`. Do đó, chúng tôi hạn chế kích thước của dữ liệu đầu vào (ảnh và mô tả) không vượt quá `30KB` để quá trình gửi đi diễn ra an toàn.

* Quy mô dữ liệu chuỗi được giới hạn ở `2KB`
* Ảnh được nén để có kích thước nhỏ hơn `28KB` bằng hàm [`imageCompression()`](https://github.com/klaytn/klaystagram/blob/main/src/utils/imageCompression.js).

## 2) Mã thành phần <a href="#id-2-component-code" id="id-2-component-code"></a>

```javascript
// src/components/UploadPhoto.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import imageCompression from 'utils/imageCompression';
import ui from 'utils/ui'
import Input from 'components/Input'
import InputFile from 'components/InputFile'
import Textarea from 'components/Textarea'
import Button from 'components/Button'

import * as photoActions from 'redux/actions/photos'

import './UploadPhoto.scss'

// Đặt giới hạn nội dung
const MAX_IMAGE_SIZE = 30 * 1024 // 30KB
const MAX_IMAGE_SIZE_MB = 30 / 1024 // 30KB

class UploadPhoto extends Component {
  state = {
    file: '',
    fileName: '',
    location: '',
    caption: '',
    warningMessage: '',
    isCompressing: false,
  }

  handleInputChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
    })
  }

  handleFileChange = (e) => {
    const file = e.target.files[0]

    /**
     * Nếu kích thước ảnh lớn hơn MAX_IMAGE_SIZE(30KB),
     * Nén ảnh để tải trên giao dịch
     * cf. Kích thước dữ liệu đầu vào giao dịch tối đa: 32KB
     */
    if (file.size > MAX_IMAGE_SIZE) {
      this.setState({
        isCompressing: true,
      })
      return this.compressImage(file)
    }

    return this.setState({
      file,
      fileName: file.name,
    })
  }

  handleSubmit = (e) => {
    e.preventDefault()
    const { file, fileName, location, caption } = this.state
    this.props.uploadPhoto(file, fileName, location, caption)
    ui.hideModal()
  }

  compressImage = async (imageFile) => {
    try {
      const compressedFile = await imageCompression(imageFile, MAX_IMAGE_SIZE_MB)
      this.setState({
        isCompressing: false,
        file: compressedFile,
        fileName: compressedFile.name,
      })
    } catch (error) {
      this.setState({
        isCompressing: false,
        warningMessage: '* Nén ảnh không thành công'
      })
    }
  }

  render() {
    const { fileName, location, caption, isCompressing, warningMessage } = this.state
    return (
      <form className="UploadPhoto" onSubmit={this.handleSubmit}>
        <InputFile
          className="UploadPhoto__file"
          name="file"
          label="Search file"
          fileName={isCompressing ? "Đang nén ảnh..." : fileName}
          onChange={this.handleFileChange}
          err={warningMessage}
          accept=".png, .jpg, .jpeg"
          required
        />
        <Input
          className="UploadPhoto__location"
          name="location"
          label="Location"
          value={location}
          onChange={this.handleInputChange}
          placeholder="Bạn đã chụp ảnh này ở đâu?"
          required
        />
        <Textarea
          className="UploadPhoto__caption"
          name="caption"
          value={caption}
          label="Caption"
          onChange={this.handleInputChange}
          placeholder="Upload your memories"
          required
        />
        <Button
          className="UploadPhoto__upload"
          type="submit"
          title="Upload"
        />
      </form>
    )
  }
}

const mapDispatchToProps = (dispatch) => ({
  uploadPhoto: (file, fileName, location, caption) =>
    dispatch(photoActions.uploadPhoto(file, fileName, location, caption)),
})

export default connect(null, mapDispatchToProps)(UploadPhoto)
```

## 3) Tương tác với hợp đồng <a href="#id-3-interact-with-contract" id="id-3-interact-with-contract"></a>

Hãy tạo một hàm để viết dữ liệu ảnh lên Klaytn. **Send transaction to contract: `uploadPhoto`**\
Không giống các lệnh gọi hàm Read-only, việc viết dữ liệu làm phát sinh phí giao dịch. Phí giao dịch được xác định bằng lượng `gas` đã sử dụng. `gas` là đơn vị đo thể hiện số lượng phép tính cần để xử lý giao dịch.

Vì những lý do này, việc gửi một giao dịch cần hai thuộc tính `from` và `gas`.

1. Chuyển đổi tập tin ảnh thành một chuỗi byte để tải trên giao dịch

   (Trong [Klaystagram contract](/content/dapp/tutorials/klaystagram/4.-write-klaystagram-smart-contract.md), chúng ta đã định nghĩa định dạng ảnh là byte trong cấu trúc `PhotoData`)

   * Đọc dữ liệu ảnh dưới dạng ArrayBuffer bằng `FileReader`
   * Chuyển đổi ArrayBuffer thành chuỗi số hex
   * Thêm tiền tố `0x` để thỏa mãn định dạng byte
2. Gọi phương pháp hợp đồng: `uploadPhoto`
   * `from`: Một tài khoản gửi giao dịch này và thanh toán phí giao dịch.
   * `gas`: Lượng gas tối đa mà tài khoản `from` sẵn sàng thanh toán cho giao dịch này.
3. Sau khi gửi giao dịch, hiển thị tiến trình cùng vòng đời giao dịch bằng thành phần `Toast`.
4. Nếu giao dịch thành công tiến vào một khối, gọi hàm `updateFeed` để thêm ảnh mới vào trang nguồn cấp dữ liệu.

```javascript
// src/redux/actions/photo.js

export const uploadPhoto = (
  file,
  fileName,
  location,
  caption
) => (dispatch) => {
  // 1. Chuyển đổi tập tin ảnh thành chuỗi số hex để tải trên giao dịch
  const reader = new window.FileReader()
  reader.readAsArrayBuffer(file)
  reader.onloadend = () => {
    const buffer = Buffer.from(reader.result)

    // Thêm tiền tố "0x" vào hexString để hợp đồng nhận diện hexString là byte
    const hexString = "0x" + buffer.toString('hex')

    // 2. Gọi phương pháp hợp đồng: uploadPhoto
    // Gửi giao dịch với tập tin ảnh (hexString) và mô tả
    try{
      KlaystagramContract.methods.uploadPhoto(hexString, fileName, location, caption).send({
        from: getWallet().address,
        gas: '200000000',
      }, (error, txHash) => {
        if (error) throw error;

        // 3. Sau khi gửi giao dịch,
        // hiển thị tiến trình cùng vòng đời giao dịch bằng thành phần "Toast".
        ui.showToast({
          trạng thái: 'đang chờ',
          message: `Gửi một giao dịch... (uploadPhoto)`,
          txHash,
        })
      })
        .then((receipt) => {
          ui.showToast({
            trạng thái: receipt.trạng thái ? 'success' : 'fail',
            message: `Đã nhận biên lai! Điều đó nghĩa là giao dịch của bạn
            ở trong khối klaytn (#${receipt.blockNumber}) (uploadPhoto)`,
            link: receipt.transactionHash,
          })

          // 4. Nếu giao dịch của bạn thành công tiến vào một khối,
          // gọi hàm "updateFeed" để thêm ảnh mới vào trang nguồn cấp dữ liệu.
          if(receipt.trạng thái) {
            const tokenId = receipt.events.PhotoUploaded.returnValues[0]
            dispatch(updateFeed(tokenId))
          }
        })
    } catch (error) {
      ui.showToast({
        trạng thái: 'error',
        message: error.toString(),
      })
    }
  }
}
```

**cf) Vòng đời giao dịch**

Sau khi gửi giao dịch, bạn có thể lấy vòng đời giao dịch (`transactionHash`, `receipt`, `error`).

* Sau khi phiên bản giao dịch đã ký của bạn được xây dựng đúng cách, sự kiện `transactionHash` sẽ được kích hoạt. Bạn có thể lấy hàm băm của giao dịch trước khi gửi giao dịch lên mạng lưới.
* Sự kiện `receipt` sẽ được kích hoạt khi bạn nhận được biên lai giao dịch. Điều đó nghĩa là giao dịch của bạn ở trong một khối. Bạn có thể xem số khối bằng `receipt.blockNumber`.
* Sự kiện `error` được kích hoạt khi có lỗi xảy ra.

## 4. Tải ảnh lên trang nguồn cấp dữ liệu: `updateFeed` <a href="#id-4-update-photo-in-the-feed-page-updatefeed" id="id-4-update-photo-in-the-feed-page-updatefeed"></a>

Sau khi thành công gửi giao dịch vào hợp đồng, FeedPage cần được cập nhật.\
Để cập nhật nguồn cấp dữ liệu ảnh, ta cần lấy dữ liệu ảnh mới ta vừa tải lên. Hãy gọi `getPhoto()` bằng `tokenId`. `tokenId` có thể được truy xuất từ biên lai giao dịch. Sau đó, thêm dữ liệu ảnh mới vào cửa hàng redux cục bộ.

```javascript
// src/redux/actions/photo.js

/**
 * 1. Gọi phương pháp hợp đồng: getPhoto()
 * Để lấy dữ liệu ảnh mới ta vừa tải lên,
 * gọi hàm "getPhoto()" bằng tokenId từ biên lai sau khi gửi giao dịch
*/
const updateFeed = (tokenId) => (dispatch, getState) => {
  KlaystagramContract.methods.getPhoto(tokenId).call()
    .then((newPhoto) => {
      const { photos: { feed } } = getState()
      const newFeed = [feedParser(newPhoto), ...feed]

      // 2. cập nhật nguồn cấp dữ liệu mới vào cửa hàng
      dispatch(setFeed(newFeed))
    })
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://archive-vn.docs.klaytn.foundation/content/dapp/tutorials/klaystagram/7.-feedpage/7-2.-uploadphoto-component.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
