1차프로젝트 - 부모/ 자식 컴포넌트의 State 관리

yojuyoon·2020년 7월 30일
0

프로젝트

목록 보기
4/10

지난주부터 끙끙앓던 문제를 해결하였고 그 문제가 도대체 무엇이였는지..
회고록을 남겨보고자 한다.

우선 내가 구현하고 있던 페이지인 29TV는 총 4개의 컴포넌트가 존재한다.

최상위 부모 컴포넌트인 TwentyNineTV컴포넌트에서 fetch를 통해 데이터를 받고, 이 데이터를 TwentyNineTVComponenet와 FeedModal이 공유한다.

TwentyNineTVComponenet와 FeedModal은 자식관계처럼 여겨졌으나 (이유는 TwentyNineTVComponenet의 이미지를 클릭 시에 FeedModal의 컴포넌트가 모달로 보여졌기 때문에... 나의 위험한 착각 ) 최상위 컴포넌트에서 데이터를 공유하는 형제관계이다.

가장 큰 문제는 무엇이였냐하면 이전에도 블로깅했었던 문제이다.
state끌어올리기

바로 heartIcon의 상태를 따로 관리하고 있다는 것..
때문에 각 컴포넌트에서 변경된 상태를 따로 반영해주고있어서 user가 feed에서 heart를 누르고, FeedModal을 열면 heart의 상태가 반영되어있지 않다는 점이다.

이 문제를 해결하기 위해서.

  1. 자식 컴포넌트에서 fetch로 통신을 하고 있으므로 여기서 변경되는 state를 함수의 인자로 전달하여 부모 컴포넌트로 보낸다.

  2. state는 최상위 부모 컴포넌트에서 관리한다.

  3. 한 컴포넌트에서 state를 관리하고(최상위 컴포넌트)
    각 컴포넌트에서 state가 변경될 때마다 다른 컴포넌트에서 변경된 state가 반영되게 한다.

ActiveLikeBtn의 상태는 클릭할 때 마다 fetch를 통해 서버와 데이터를 주고받는다. 이 함수는 ActiveLikeBtn의 내부에서 이루어진다. 함수를 살펴보면

 iconHandler = () => {
    const { postId, handleIcon } = this.props;

    console.log(handleIcon);
    fetch(`http://${API_URL}/media/recommend/like`, {
      method: "PATCH",
      headers: {
        Authorization: localStorage.getItem("token"),
      },
      body: JSON.stringify({
        post: postId,
      }),
    })
      .then((res) => res.json())
      .then((res) => {
        const option = {
          user_likes_pressed: res.data.user_likes_pressed,
          like_num: res.data.like_num,
        };
        handleIcon(postId, option);
      });
  };

PATCH함수는 리소스를 일부 수정할 때 쓰는 메소드이다.
-> HTTP 응답 코드 메소드 정리 참고

user가 클릭시에 어떤 post를 클릭하였는지의 여부를 알기 위해 postId를 넘겨주면 icon의 상태를 다시 전달받는다. 이를 option 변수에 담아서 최상위 부모 컴포넌트인 TwentyNineTV에 있는 handleIcon함수에 인자로 넘겨준다.

여기서 비동기 문제가 발생하였다.

heartIcon의 상태를 변경해주는 함수는 ActiveLIkeBtn컴포넌트 안에서 이루어지고 이는 가장 최하단의 컴포넌트인데, 여기서 변경된 상태를 부모로 끌어올려주기위해 this.props.handleIcon함수를 사용해 인자로 변경된 상태를 넘겨주었는데,

문제는 FeedModal에서는 heartIcon을 누를 경우 각 Feed에 반영 되지만, TwentyNineTVComponent에서는 함수 자체가 undefined로 뜨는 것이였다.
Uncaught (in promise) TypeError 발생...

문제 해결

최상단 부모 컴포넌트인 TwentyNineTV 컴포넌트의 handleIcon함수를 살펴보면

  handleIcon = (clickedId, option) => {
    this.setState({
      data: this.state.data.map((feed) => {
        if (feed.post_id === clickedId) {
          return {
            ...feed,
            ...option,
          };
        }
        return feed;
      }),
    });
  };

인자로 들어온 postId와 option변수를 이용하여 data 자체의 상태를 변경해준다.
어떤 post의 LikeBtn을 눌렀는지 알기 위해서 postId를 넘겨준 것이고 이 postId가 ClickedId와 같을 경우 변경된 데이터의 상태로 바꿔주는 것이다. (좋아요 누른 갯수가 +되고 heartIcon의 상태는 boolean으로 관리하고있기 때문에 true혹은 false로)

이 함수를 자식 컴포넌트로 어떻게 내려주고 있느냐 하면

       {data.map((feed, index) => {
          return (
 	<TwentyNineTVFeedComponent
              onClick={() => {
                showModal();
                setModalIdx(index);
              }}
              feed={feed}
              key={index}
              handleIcon={handleIcon} //before : 구조분해할당으로 넘겨줌
handleIcon={this.handleIcon}//after : 구조분해할당 하지않고 넘겨줌

            />
    	);
      })}
      
         <FeedModal handleIcon={handleIcon}/>
              

이렇게 내려주고 있었는데...before에서 살펴볼 수 있듯이 코드 리팩토링을 하면서 this를 destructuring을 해주고 함수명만 보내고있었는데, 이 부분이 문제였다.

비동기 문제로 인하여 destructuring 한 변수를 읽기 전에 함수가 실행되므로... undefined가 발생했던 것이다.

또한 ActiveLikeBtn에 props 로 보내는 TwentyNineTVComponent에서도 마찬가지로 destructuring을 사용하여 함수명만 보내고 있었는데,

     <ActiveLikeBtn
        handleIcon={handleIcon}//before
		handleIcon={this.props.handleIcon}//after
        postId={post_id}
        likedNumber={like_num}
        heartState={user_likes_pressed}
      />

이 또한 destructuring을 하지 않고 보내주어야 한다.

비동기를 이해하고 처리하는 데에는 좀 더 경험이 필요할 것 같고,
구조 분해 할당이 코드를 간결하게 보여줄 수는 있겠지만 이러한 상황에서는 오류를 일으킬 수도 있다는 경험을 통하여...이런 상황에서는 주의하여 사용해야한다는 것을 배웠다.

그럼 이제 리팩토링을 하러 가볼까나...총총

잘못된 부분이 있다면 언제든지 댓글로 지적 부탁드립니다.

profile
하고싶은게 많은 사람. Front-end Developer

0개의 댓글