Index 페이지에 Story 제목 출력하기

index 페이지를 수정하려고 합니다. 왜냐하면 index 페이지에 DreamStory들의 요약정보들이 나와야 하는데, 지금은 단순히 컨트랙트 주소들만 나와 있어서 무슨 스토리인지 알 수가 없기 때문입니다.

고생고생해서 결과는 얻었지만, 워낙 삽질에 지저분하게 구현되어 좀 더 깔끔하게 해보려고 몇 가지 했봤습니다. 그런데 실패했습니다. 그래서 지저분하더라도 성공한 구현 방법을 설명하겠습니다.

구현하고자 했던 것은 Index 페이지에 DreamStory 컨트랙트 주소가 아니라 스토리 제목이 보이게 하는 것이었습니다. 물론 컨트랙트 주소는 쓸모 없지 않고, 상세 페이지 이동할 때 매우 중요하게 사용됩니다만, 그건 사용자가 몰라도 되는 부분입니다. 사용자가 직접 접속하는 index 페이지에 컨트랙트 주소가 표시되는 것은 별 의미가 없죠.

매우 간단하게 할 수 있겠거니 했는데... 이리 해봐도, 저리 해봐도 안됐습니다. 이런 오류가 많이 발생했던 거 같습니다.
image.png

자세한 설명은 생략하고, 구현한 방법만 소개하겠습니다.


DreamFactory 컨트랙트 수정

DreamFactory 컨트랙트 소스 코드를 왠만하면 수정하지 않으려고 했는데, 할 수 밖에 없었습니다. 위와 같은 에러가 발생하여 감당이 안 됐습니다.

// dream story factory contract
contract DreamFactory {
    // array of addresses of deployed dream stories
    address[] public deployed_dream_stories;
    // list of stories titles
    mapping( address => string ) public stories_titles;
    /*
     * Create a new dream story
     * @param min_down_price minimum download price in wei
     * @param story dream story
     */
    function createDreamStory( uint _min_down_price, string _title, string _story ) public {
        // create a new dream story
        address new_story= new DreamStory( _min_down_price, msg.sender, _title, _story );
        // save the deployed address
        deployed_dream_stories.push( new_story );
        // save the story title
        stories_titles[new_story]= _title;
    }
(생략)

Index 페이지에서 DreamFactory 인스턴스를 이용하여 스토리 제목을 얻기 위한 mapping 변수를 하나 추가했습니다.
mapping( address => string ) public stories_titles;
그리고 새로운 스토리가 만들어 질 때마다 위 변수에 제목을 추가하게 했구요. 참고로 상태변수를 public으로 선언해야지만 참조할 수 있는 함수가 자동으로 생성됩니다.

컨트랙트 소스 코드가 변경되면 컴파일 후 재배포 작업이 필요합니다. 이젠 정말 많이 익숙합니다. ethereum 폴더로 이동해서 아래와 같이 실행합니다.

$ node compile.js
$ node deploy.js

컴파일 후에는 반드시 에러가 발생하는지 검토해야 합니다. 가장 확실한 방법은 Remix로 이동하여 문법 오류나 동작 테스틑를 하는 것입니다.


index 페이지 수정

컨트랙트 소스를 수정하지 않고 원래 생각했던 방법은 DreamFactory의 인스턴스를 이용하여 개별 DreamStory 인스턴스를 얻을 수 있으니 스토리 제목을 쉽게 얻으려고 했습니다. 그러나 실패!

그냥 아래 방법이 지저분하더라도 그냥 이해 바랍니다.


getInitialProps 함수 수정

  static async getInitialProps() {
    // get dream factory instance
    const stories= await dream_factory.methods.getDeployedDreamStories().call();
      // Generate array of indices and fill them
    const titles= await Promise.all(
      Array( parseInt(stories.length) ).fill().map( (element, index) => {
        return dream_factory.methods.stories_titles(stories[index]).call();
      })
    );

    return { stories, titles };
  }

  • map함수를 이용하여 titles 변수에 스토리 제목들을 저장
  • 저장된 titles을 반환에 추가

이렇게 한 이유는 먼저 솔리디티에서 string 배열을 반환하는 것이 현재는 허용이 안되기 때문입니다. 향후 될 예정이라 하네요. 여기서 부터 꼬이지 시작했죠.
그리고 솔리디티의 mapping 변수는 주소를 모르면 제목을 가져올 수가 없습니다. 배열처럼 indexing해서 얻어 오는 방법이 없습니다. 그래서 이런 꼼수를 사용한 것입니다.
Promise.all 이것은 컨트랙트에서 값을 얻어오는 결과를 기다려야 하기 때문에 추가됐습니다.


renderStories 함수 수정

getInitialProps에서 리턴한 stories 주소와 제목을 이용하여 제목을 화면에 출력하는 코드를 만들었습니다. 이를 위해 renderStories 함수를 다음과 같이 수정합니다.

// render card groups to display stories
  renderStories() {
    // map calls the arugment function one time for every element inside stories array
    const items= Object.entries(this.props.stories).map( (address, index) => {
      return {
        header: this.props.titles[index],
        description: (
          <Link route={`/dream_stories/${address[1]}`}>
            <a>View Story Details</a>
          </Link>
        ),
        meta: address,
        fluid: true
      };
    });

    return <Card.Group items= {items} />;
  }

  • const items= Object.entries(this.props.stories).map( (address, index) => { 이 부분은 stories 주소를 index로 참조하기 위한 것임
  • header: this.props.titles[index] 이 부분을 통해서 titles 변수에서 index에 해당하는 제목을 추출함
  • <Link route={/dream_stories/${address[1]}}> 여기서 ${address[1]}로 표시한 이유는 address가 {0, address} 형태이기 때문임. 이것은 Object.entries로 인해 변경된 것으로 보임. 그래서 첫번째 인자를 무시하고, 컨트랙트 주소가 들어있는 두번째 인자만 선택하여 라우팅함.

여기서도 생쑈를 했습니다. titles 변수를 dictionary 타입으로 만들었는데, 왜 그런지 모르겠지만 바깥에 대괄호가 하나 씌어지게 됐고, 그것을 없앨 수가 없었습니다. 브라우저 콘솔에서 일반적인 dictionary 타입 변수로 했을 때는 쉽게 대괄호를 없앨 수 있었는데 이 경우에서는 대괄호를 없애면 titles의 내용 중 첫 번째 내용만 남고 나머지는 사라졌습니다. 저도 이유를 모르겠습니다!

그리하여 제가 택한 방법은, titles 변수를 dictionary 타입으로 사용하지 못하고, story 주소를 index로 바꿔서 그 index를 이용하여 특정 제목을 뽑아내는 것입니다. 결과적으로 가능은 하지만 왠지 번거롭게 한다리 거치는 거라 깔끔하지 않습니다.

새롭게 컨트랙트 배포하고 스토리도 새롭게 적어봤습니다. 각 스토리의 컨트랙트 주소도 표시를 해봤습니다. 왠지 컨트랙트 주소가 표시되면 신뢰도가 올라갈거 같은 느낌이네요.
image.png


이것으로 DreamChain Dapp 대장정을 일단락 지으려고 합니다. 응원해주신 분들 너무 고맙습니다. 애착이 가는 프로젝트라 앞으로도 더욱 개선되고 업데이트 될 것입니다. 아쉬워 하지 마세요 ^^;

이제부터가 시작입니다!



오늘의 실습: 자. 이제 정말 자신의 드림 스토리를 써봅시다! 어떤 영감을 받았나요? 어떤 자신의 문제점을 발견했나요?



 [한빛미디어 블록체인 도서 보러가기]