# 7-2. UploadPhoto Component

![사진 업로드](https://github.com/klaytn/klaytn-docs-ko/blob/main/docs/bapp/tutorials/klaystagram/images/klaystagram-uploadphoto.png)

1. `UploadPhoto` 컴포넌트의 역할
2. 컴포넌트 코드
3. 컨트랙트와의 상호작용
4. 리덕스 스토어의 데이터 업데이트: `updateFeed` 함수

## 1) `UploadPhoto` component's role <a href="#id-1-uploadphoto-component-s-role" id="id-1-uploadphoto-component-s-role"></a>

`UploadPhoto` 컴포넌트는 Klaytn 블록체인에 사진을 업로드하는 요청을 처리합니다. 다음과 같은 과정을 거쳐 이루어집니다.

1. 트랜잭션을 전송하여 스마트 컨트랙트의 `uploadPhoto` 메서드를 호출합니다. `uploadPhoto` 메서드에서 새로운 ERC-721 토큰이 발행됩니다.
2. 트랜잭션 전송 후 `Toast` 컴포넌트를 사용하여 트랜잭션 생명 주기로 진행 상황을 나타냅니다.
3. 트랜잭션이 블록에 담기면 로컬의 리덕스 스토어에서 새로운 `PhotoData`로 업데이트합니다.

**Limiting content size**\
The maximum size of a single transaction is `32KB`. So we restrict the input data (photo and descriptions) not to exceed `30KB` to send it over safely.

* 문자열 데이터의 크기는 `2KB`로 제한됩니다.
* Photo is compressed to be less than `28KB` using [`imageCompression()`](https://github.com/klaytn/klaystagram/blob/main/src/utils/imageCompression.js) function.

## 2) Component code <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'

// Set a limit of contents
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]

    /**
     * If image size is bigger than MAX_IMAGE_SIZE(30KB),
     * Compress the image to load it on transaction
     * cf. Maximum transaction input data size: 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: '* Fail to compress image'
      })
    }
  }

  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 ? 'Compressing image...' : 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="Where did you take this photo?"
          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) Interact with contract <a href="#id-3-interact-with-contract" id="id-3-interact-with-contract"></a>

Klaytn에 사진 데이터를 쓰는 함수를 만들어보겠습니다. **Send transaction to contract: `uploadPhoto`**\
Unlike read-only function calls, writing data incurs a transaction fee. 트랜잭션 수수료는 사용한 `gas`의 양에 따라 결정됩니다. `gas`는 트랜잭션을 처리하는 데에 필요한 계산량을 나타내는 측정 단위입니다.

따라서 트랜잭션을 보내려면 `from`과 `gas` 두 속성이 있어야 합니다.

1. 트랜잭션에 올릴 수 있도록 사진 파일을 바이트 문자열로 변환합니다.

   (In [Klaystagram contract](/content/dapp/tutorials/klaystagram/4.-write-klaystagram-smart-contract.md), we defined photo fotmat as bytes in `PhotoData` struct)

   * `FileReader`를 사용하여 ArrayBuffer로 사진 데이터를 읽어옵니다.
   * ArrayBuffer를 16진수 문자열로 변환합니다.
   * Add Prefix `0x` to satisfy bytes format
2. Invoke the contract method: `uploadPhoto`
   * `from`: An account that sends this transaction and pays the transaction fee.
   * `gas`: The maximum amount of gas that the `from` account is willing to pay for this transaction.
3. After sending the transaction, show progress along the transaction lifecycle using `Toast` component.
4. If the transaction successfully gets into a block, call `updateFeed` function to add the new photo into the feed page.

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

export const uploadPhoto = (
  file,
  fileName,
  location,
  caption
) => (dispatch) => {
  // 1. Convert photo file as a hex string to load on transaction
  const reader = new window.FileReader()
  reader.readAsArrayBuffer(file)
  reader.onloadend = () => {
    const buffer = Buffer.from(reader.result)

    // Add prefix `0x` to hexString to recognize hexString as bytes by contract
    const hexString = "0x" + buffer.toString('hex')

    // 2. Invoke the contract method: uploadPhoto
    // Send transaction with photo file(hexString) and descriptions
    try{
      KlaystagramContract.methods.uploadPhoto(hexString, fileName, location, caption).send({
        from: getWallet().address,
        gas: '200000000',
      }, (error, txHash) => {
        if (error) throw error;

        // 3. After sending the transaction,
        // show progress along the transaction lifecycle using `Toast` component.
        ui.showToast({
          status: 'pending',
          message: `Sending a transaction... (uploadPhoto)`,
          txHash,
        })
      })
        .then((receipt) => {
          ui.showToast({
            status: receipt.status ? 'success' : 'fail',
            message: `Received receipt! It means your transaction is
            in klaytn block (#${receipt.blockNumber}) (uploadPhoto)`,
            link: receipt.transactionHash,
          })

          // 4. If the transaction successfully gets into a block,
          // call `updateFeed` function to add the new photo into the feed page.
          if(receipt.status) {
            const tokenId = receipt.events.PhotoUploaded.returnValues[0]
            dispatch(updateFeed(tokenId))
          }
        })
    } catch (error) {
      ui.showToast({
        status: 'error',
        message: error.toString(),
      })
    }
  }
}
```

**cf) Transaction life cycle**

After sending transaction, you can get transaction life cycle (`transactionHash`, `receipt`, `error`).

* `transactionHash` 이벤트는 서명된 트랜잭션 인스턴스가 올바르게 구성되면 발생합니다. 네트워크에 트랜잭션을 전송하기 전에 트랜잭션 해시를 구할 수 있습니다.
* `receipt` 이벤트는 트랜잭션 영수증을 받을 때 발생합니다. 이는 트랜잭션이 블록에 들어갔음을 의미합니다. `receipt.blockNumber`으로 블록 번호를 확인할 수 있습니다.
* `error` 이벤트는 어떤 문제가 있을 때 발생합니다.

## 4. 피드 페이지의 사진 업데이트: `updateFeed` <a href="#id-4-update-photo-in-the-feed-page-updatefeed" id="id-4-update-photo-in-the-feed-page-updatefeed"></a>

After successfully sending the transaction to the contract, FeedPage needs to be updated.\
In order to update the photo feed, we need to get the new photo data we've just uploaded. `tokenId`와 함께 `getPhoto()`를 호출합니다. `tokenId`는 트랜잭션 영수증에서 찾을 수 있습니다. 그다음 새 사진의 데이터를 로컬 리덕스 스토어에 저장하세요.

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

/**
 * 1. Call contract method: getPhoto()
 * To get new photo data we've just uploaded,
 * call `getPhoto()` with tokenId from receipt after sending transaction
*/
const updateFeed = (tokenId) => (dispatch, getState) => {
  KlaystagramContract.methods.getPhoto(tokenId).call()
    .then((newPhoto) => {
      const { photos: { feed } } = getState()
      const newFeed = [feedParser(newPhoto), ...feed]

      // 2. 리덕스 스토어에 새로운 피드를 업데이트합니다.
      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-ko.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.
