13-5. 덧글 창 토글 하기

이제 기본적으로 덧글 창을 숨기고, 덧글 아이콘이 클릭될 때 마다 열고 닫는 기능을 구현해보겠습니다.

CommentBlockContainer 에 상태 연결하기

이번에 CommentBlockContainer 를 연결할때는, 지금까지 리덕스의 상태를 연결 할 때 해왔던것과 조금 다릅니다. connect 의 첫번째 파라미터로 들어가는 함수에서 (state) 가 아닌 (state, ownProps) 이렇게 두개의 값이 들어가게 하였는데요, 여기서 ownProps 는 컴포넌트가 렌더링 될 때 부모 컴포넌트에게서 받은 props 값을 가르킵니다.

우리는 나중에 이 컴포넌트를 렌더링 하게 될 때, post 객체를 props 로 전달받게됩니다. 그리고, 상태연결이 되는 과정에서, 부모 컴포넌트에게서 받은 post 의 _id 값을 기준으로, comments Map의 데이터를 참조합니다.

그 다음에는, 렌더 함수에서 status 를 toJS() 를 통해 객체로 변환하고 visible 값과 value 값에 대한 레퍼런스를 만드세요. 덧글 창이 처음 열리기 전엔, status 가 존재하지 않기 때문에 status 가 undefined 이기 때문에, 만약에 status 가 없을때는 객체 비구조화 과정에서 비어있는 객체 { } 를 사용하도록 합니다.

visible 값이 true 가 아닐때는, null 을 반환함으로서 아무것도 보여주지 않도록 설정하세요.

src/containers/Shared/PostList/CommentBlockContainer.js

import React, { Component } from 'react';
import CommentBlock from 'components/Shared/PostList/CommentBlock';
import { connect } from 'react-redux';

class CommentBlockContainer extends Component {
    render() {
        const { status } = this.props;
        const { visible, value } = status ? status.toJS() : { }; // status 가 존재하지 않는 경우를 위한 예외 케이스

        if(!visible) return null; // visible 이 false 면 아무것도 렌더링하지 않기

        return (
            <CommentBlock 
                value={value}
            />
        );
    }
}

export default connect(
    (state, ownProps) => ({
        // ownProps 에는 이 컴포넌트가 부모 컴포넌트에게서 받을 props 를 가르킵니다.
        // 전달받는 post 를 기준으로, 해당 comments Map 의 주어진 post 의 _id 키를 가진 상태값을 가져옵니다
        status: state.posts.getIn(['comments', ownProps.post.get('_id')])
    })
)(CommentBlockContainer);

현재는 아직 우리가 컴포넌트에 post 값을 전달해주지 않아서 에러가 발생하게 됩니다. Post 컴포넌트에서 CommentBlockContainer 를 렌더링 할 때 post 를 전달해주도록 설정하세요.

src/components/Shared/PostList/Post.js - CommentBlockContainer 렌더링

<CommentBlockContainer post={post}/>

덧글버튼이 클릭 됐을 때 실행 할 함수 만들기

이제, PostListContainer 에서 덧글버튼이 클릭 됐을때 실행되는 함수인 handleCommentClick 메소드를 정의하고, 이를 PostList 에 전달해주세요.

src/containers/Shared/PostList/PostListContainer.js

(...)

class PostListContainer extends Component {
    (...)

    handleCommentClick = (postId) => {
        const { PostsActions } = this.props;
        PostsActions.toggleComment(postId);
    }

    render() {
        const { data } = this.props;
        const { handleToggleLike, handleCommentClick } = this;

        return (
            <PostList 
                posts={data} 
                onToggleLike={handleToggleLike}
                onCommentClick={handleCommentClick}
            />
        );
    }
}

(...)

이제, 이 메소드를 PostFooter 까지 전달을 해주어야 하는데요, 이 때 수정해야 할 컴포넌트가 꽤 많으니 차근 차근 따라와주세요.

우선, PostList 부터 수정을 해줍시다. 각 Post 에 onCommentClick 을 전달하세요.

src/components/Shared/PostList/PostList.js

(...)
const PostList = ({posts, onToggleLike, onCommentClick}) => {
    const postList = posts.map(
        (post) => (
            <Post key={post.get('_id')} post={post} onToggleLike={onToggleLike} onCommentClick={onCommentClick}/>
        )
    )
    (...)
}

그 다음엔 Post 컴포넌트에서, _id 값을 넣어서 함수를 호출하도록, 함수를 감싸서 새 함수를 정의하세요. 그리고, 그 함수를 onCommentClick props 로 PostFooter 에 전달합니다.

src/components/Shared/PostList/Post.js

(...)
const Post = ({post, onToggleLike, onCommentClick}) =>{
    (...)

    const commentClick = () => commentClick(_id);

    return (
        <Wrapper>
            (...)
            <PostFooter likesCount={likesCount} liked={liked} onToggleLike={toggleLike} onCommentClick={commentClick}/>
            <CommentBlockContainer post={post}/>
        </Wrapper>
    )
}

마지막으로 PostFooter 에서 전달받은 onCommentClick 을, CommentIcon 이 클릭 됄 때 실행되도록 onClick 값으로 설정하세요.

src/components/Shared/PostList/PostFooter.js

(...)
const PostFooter = ({liked, likesCount=0, comments=[], onToggleLike, onCommentClick}) => (
    <Wrapper>
        <Likes active={liked}>
            <HeartIcon onClick={onToggleLike}/>
            <span>좋아요 {likesCount}개</span>
        </Likes>
        <Comments>
            <CommentIcon onClick={onCommentClick}/>
            <span>덧글 {comments.length}개</span>
        </Comments>
    </Wrapper>
);

export default PostFooter;

여기까지 하면, 덧글버튼을 눌렀을때, 덧글 창이 나타났다가, 사라집니다. 하지만, 레이아웃은 업데이트 되지 않아서 다음과 같은 현상이 발생하게 됩니다.

이렇게, 카드가 겹치는 현상이 발생하게되는데요, 이를 해결하려면, Masonry 컴포넌트에 ref 를 달아주고, masonry 의 layout() 함수를 실행시켜주어야합니다.

PostList 에서 Masonry 에 ref 달아주기

Masonry 컴포넌트를 렌더링 하게 될 때, ref 를 설정하겠습니다. 이 때, ref 값으로 전달되는 함수는 PostListContainer 에서 정의하여 받아오도록 합니다.

src/components/Shared/PostList/PostList.js

(...)

const PostList = ({posts, onToggleLike, onCommentClick, masonryRef}) => {
    const postList = posts.map(
        (post) => (
            <Post key={post.get('_id')} post={post} onToggleLike={onToggleLike} onCommentClick={onCommentClick}/>
        )
    )
    return (
        <Wrapper>
            <Masonry options={{gutter: 16}} ref={masonryRef}>
                {postList}
            </Masonry>
        </Wrapper>
    );
}

export default PostList;

그 다음엔, PostListContainer 에서 PostList 에 masonryRef 를 설정해주고, handleCommentClick 메소드가 실행 될 때, 우리가 만든 ref를 사용하여 layout() 을 호출하도록 작성하세요. 이 때, this.masonry 가 아닌 this.masonry.masonry 입니다. 그 이유는, 여기서 this.masonry 는 Masonry 컴포넌트를 가르키고, this.masonry.masonry 가, 실제 masonry 라이브러리 인스턴스를 가르키기 때문입니다.

이 함수를 호출하게 될 때, setTimeout(() => this.masonry.masonry.layout(), 0) 이런식으로 setTimeout 으로 감싸주어야, 다른 이벤트루프에서 작동하기 때문에, 덧글창이 열리고 나서 함수가 호출되게 됩니다. 만약에 setTimeout 을 하지 않으면 레이아웃이 toggleComment 작업이 마치기전에 실행되어 리레이아웃이 한발짝 늦게 처리되는것처럼 나타나게 됩니다.

src/containers/Shared/PostList/PostListContainer.js

(...)
class PostListContainer extends Component {
    (...)

    handleCommentClick = (postId) => {
        const { PostsActions } = this.props;
        PostsActions.toggleComment(postId);
        setTimeout(() => this.masonry.masonry.layout(), 0);
    }

    render() {
        const { data } = this.props;
        const { handleToggleLike, handleCommentClick } = this;

        return (
            <PostList 
                posts={data} 
                onToggleLike={handleToggleLike}
                onCommentClick={handleCommentClick}
                masonryRef={ref => this.masonry = ref}
            />
        );
    }
}

(...)

이제 덧글 창 토글 기능이 완료되었습니다.

results matching ""

    No results matching ""