트랜잭션 전송 후 Toast 컴포넌트를 사용하여 트랜잭션 생명 주기로 진행 상황을 나타냅니다.
트랜잭션이 블록에 담기면 로컬의 리덕스 스토어에서 새로운 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() function.
2) Component code
// src/components/UploadPhoto.jsimport 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 contentsconstMAX_IMAGE_SIZE=30*1024// 30KBconstMAX_IMAGE_SIZE_MB=30/1024// 30KBclassUploadPhotoextendsComponent { state = { file:'', fileName:'', location:'', caption:'', warningMessage:'', isCompressing:false, }handleInputChange= (e) => {this.setState({ [e.target.name]:e.target.value, }) }handleFileChange= (e) => {constfile=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, })returnthis.compressImage(file) }returnthis.setState({ file, fileName:file.name, }) }handleSubmit= (e) => {e.preventDefault()const { file,fileName,location,caption } =this.statethis.props.uploadPhoto(file, fileName, location, caption)ui.hideModal() }compressImage=async (imageFile) => {try {constcompressedFile=awaitimageCompression(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.statereturn ( <formclassName="UploadPhoto"onSubmit={this.handleSubmit}> <InputFileclassName="UploadPhoto__file"name="file"label="Search file"fileName={isCompressing ?'Compressing image...': fileName}onChange={this.handleFileChange}err={warningMessage}accept=".png, .jpg, .jpeg"required /> <InputclassName="UploadPhoto__location"name="location"label="Location"value={location}onChange={this.handleInputChange}placeholder="Where did you take this photo?"required /> <TextareaclassName="UploadPhoto__caption"name="caption"value={caption}label="Caption"onChange={this.handleInputChange}placeholder="Upload your memories"required /> <ButtonclassName="UploadPhoto__upload"type="submit"title="Upload" /> </form> ) }}constmapDispatchToProps= (dispatch) => ({uploadPhoto: (file, fileName, location, caption) =>dispatch(photoActions.uploadPhoto(file, fileName, location, caption)),})exportdefaultconnect(null, mapDispatchToProps)(UploadPhoto)
3) Interact with contract
Klaytn에 사진 데이터를 쓰는 함수를 만들어보겠습니다. Send transaction to contract: uploadPhoto
Unlike read-only function calls, writing data incurs a transaction fee. 트랜잭션 수수료는 사용한 gas의 양에 따라 결정됩니다. gas는 트랜잭션을 처리하는 데에 필요한 계산량을 나타내는 측정 단위입니다.
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.
After sending the transaction, show progress along the transaction lifecycle using Toast component.
If the transaction successfully gets into a block, call updateFeed function to add the new photo into the feed page.
// src/redux/actions/photo.jsexportconstuploadPhoto= ( file, fileName, location, caption) => (dispatch) => {// 1. Convert photo file as a hex string to load on transactionconstreader=newwindow.FileReader()reader.readAsArrayBuffer(file)reader.onloadend= () => {constbuffer=Buffer.from(reader.result)// Add prefix `0x` to hexString to recognize hexString as bytes by contractconsthexString="0x"+buffer.toString('hex')// 2. Invoke the contract method: uploadPhoto// Send transaction with photo file(hexString) and descriptionstry{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) {consttokenId=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
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는 트랜잭션 영수증에서 찾을 수 있습니다. 그다음 새 사진의 데이터를 로컬 리덕스 스토어에 저장하세요.
// 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*/constupdateFeed= (tokenId) => (dispatch, getState) => {KlaystagramContract.methods.getPhoto(tokenId).call().then((newPhoto) => {const { photos: { feed } } =getState()constnewFeed= [feedParser(newPhoto),...feed]// 2. 리덕스 스토어에 새로운 피드를 업데이트합니다.dispatch(setFeed(newFeed)) })}