2-4. 포스트 수정 및 삭제하기
이제 거의 작업을 마쳤습니다. 이번엔 포스트의 수정과 삭제를 구현 할 차례입니다.
헤더에 버튼 보여주기

포스트를 읽는 페이지에서 상단에 새 포스트 좌측에 두개의 버튼을 추가해주겠습니다. 수정 버튼의 경우엔 /editor?postId=ID 링크로 이동하도록 설정하고, 삭제의 경우엔 onRemove 라는 함수를 props 로 받아와서 호출하게 설정하겠습니다.
우선, HeaderContainer 를 만들어서 포스트 페이지인 경우에 포스트 아이디를 전달하도록 설정하세요.
src/containers/common/HeaderContainer.js
import React, { Component } from 'react';
import Header from 'components/common/Header';
import { withRouter } from 'react-router-dom';
class HeaderContainer extends Component {
handleRemove = () => {
// 미리 만들어두기
}
render() {
const { handleRemove } = this;
const { match } = this.props;
const { id } = match.params;
return (
<Header
postId={id}
onRemove={handleRemove}
/>
);
}
}
export default withRouter(HeaderContainer);
아직은 리덕스와 연결되어있지 않지만, 나중에 우리가 관리자 기능을 로그인 구현을 하게 될 때 connect 를 사용하게 되므로, containers 디렉토리에 이렇게 컨테이너 컴포넌트를 생성해주었습니다.
이 컴포넌트를 만들고, PageTemplate 에서 Header 를 대체해주세요.
src/components/common/PageTemplate/PageTemplate.js
import React from 'react';
import styles from './PageTemplate.scss';
import classNames from 'classnames/bind';
import HeaderContainer from 'containers/common/HeaderContainer';
import Footer from 'components/common/Footer';
const cx = classNames.bind(styles);
const PageTemplate = ({children}) => (
<div className={cx('page-template')}>
<HeaderContainer/>
<main>
{children}
</main>
<Footer/>
</div>
);
export default PageTemplate;
이제, params 에 id 가 있는 경우 해당 값을 Header 로 전달을 해줍니다. Header 컴포넌트를 열어서, postId 를 전달 받았을 경우에 두 버튼이 보여지도록 설정하세요.
src/components/common/Header/Header.js
(...)
const Header = ({postId, onRemove}) => (
<header className={cx('header')}>
<div className={cx('header-content')}>
<div className={cx('brand')}>
<Link to="/">reactblog</Link>
</div>
<div className={cx('right')}>
{
// flex 를 유지하기 위하여 배열 형태로 렌더링 합니다.
postId && [
<Button key="edit" theme="outline" to={`/editor?id=${postId}`}>수정</Button>,
<Button key="remove" theme="outline" onClick={onRemove}>삭제</Button>
]
}
<Button theme="outline" to="/editor">새 포스트</Button>
</div>
</div>
</header>
);
export default Header;
수정 버튼을 누르면, /editor/?id=ID 페이지로 전환하도록 설정하였습니다. 그리고, 삭제 버튼을 누르면 props 로 전달받은 onRemove 를 호출하도록 설정했습니다. 이 함수는, 추후 구현 될 것이며, 우선 수정 기능부터 구현을 해보겠습니다.
수정 기능 구현하기
에디터에서 포스트정보 불러오기
수정 버튼을 클릭하여 에디터 페이지로 오게 되면 id 라는 쿼리가 설정됩니다. 에디터가 열릴 때, 이 id 값이 존재한다면, 해당 포스트의 내용을 불러와서 editor 의 상태에 넣어주겠습니다.
기존에 만들었던 getPost API 함수를 재활용 하여, editor 모듈에 GET_POST 액션을 만드세요.
src/store/modules/editor.js
(...)
const GET_POST = 'editor/GET_POST';
// action creators
(...)
export const getPost = createAction(GET_POST, api.getPost);
// initial state
const initialState = Map({
title: '',
markdown: '',
tags: '',
postId: null
});
// reducer
export default handleActions({
(...)
...pender({
type: GET_POST,
onSuccess: (state, action) => {
const { title, tags, body } = action.payload.data;
return state.set('title', title)
.set('markdown', body)
.set('tags', tags.join(', ')); // 배열 -> ,로 구분된 문자열
}
})
}, initialState)
그 다음엔, EditorHeaderContainer 를 열어서 componentDidMount 부분에서 initialize 를 실행한 다음에 쿼리값이 들어있는 문자열 형태인 location.search 값을 파싱하여 이 안에 id 값이 들어있으면 GET_POST 를 실행하도록 코드를 작성해보세요.
src/containers/editor/EditorHeaderContainer.js
import queryString from 'query-string';
class EditorHeaderContainer extends Component {
componentDidMount() {
const { EditorActions, location } = this.props;
EditorActions.initialize(); // 에디터를 초기화 합니다.
// 쿼리 파싱
const { id } = queryString.parse(location.search);
if(id) {
// id 가 존재하는 경우 포스트 불러오기
EditorActions.getPost(id);
}
}
(...)
코드를 저장하고 나서, 포스트를 수정 할 때, 초기 데이터를 제대로 불러와주는지 확인 해보세요.

수정 API 함수 및 액션 만들기
수정 API 함수는 writePost 함수와 비슷하지만, axios.patch 를 사용하고, id 값을 추가적으로 받습니다.
api 파일을 열어서 다음 함수를 추가하세요
src/lib/api.js
(…)
export const editPost = ({id, title, body, tags}) => axios.patch(`/api/posts/${id}`, { title, body, tags});
이제 이 함수를 호출하는 액션을 준비해줍시다.
여기서는, 리듀서 부분 (handleActions) 에서 따로 로직을 작성 할 필요가 없습니다. 이미 현재 어떤 포스트를 수정하는지 알고 있으니까요.
src/store/modules/editor.js
import { createAction, handleActions } from 'redux-actions';
import { Map } from 'immutable';
import { pender } from 'redux-pender';
import * as api from 'lib/api';
// action types
(...)
const EDIT_POST = 'editor/EDIT_POST';
// action creators
(...)
export const editPost = createAction(EDIT_POST, api.editPost);
(...)
액션을 작성하셨다면, EditorHeaderContainer 를 마저 구현하겠습니다. handleSubmit 부분에서, id 값이 존재한다면 writePost 가 아닌 editPost 를 호출하도록 설정하세요.
그 다음엔, 렌더링 부분에서 id값이 있는 경우 isEdit 이라는 props를 true 로 설정하겠습니다.
src/containers/editor/EditorHeaderContainer.js
(...)
class EditorHeaderContainer extends Component {
(...)
handleSubmit = async () => {
const { title, markdown, tags, EditorActions, history, location } = this.props;
const post = {
title,
body: markdown,
// 태그 텍스트를 , 로 분리시키고 앞뒤 공백을 지운 후 중복 되는 값을 제거해줍니다.
tags: tags === "" ? [] : [...new Set(tags.split(',').map(tag => tag.trim()))]
};
try {
// id 가 존재하는 경우 editPost 호출
const { id } = queryString.parse(location.search);
if(id) {
await EditorActions.editPost({ id, ...post });
history.push(`/post/${id}`);
return;
}
await EditorActions.writePost(post);
// 페이지를 이동시킵니다. 주의: postId 를 상단에서 레퍼런스를 만들지 않고
// 이 자리에서 this.props.postId 를 조회해주어야합니다. (현재의 값을 불러오기 위함)
history.push(`/post/${this.props.postId}`);
} catch (e) {
console.log(e);
}
}
render() {
const { handleGoBack, handleSubmit } = this;
const { id } = queryString.parse(this.props.location.search);
return (
<EditorHeader
onGoBack={handleGoBack}
onSubmit={handleSubmit}
isEdit={id ? true : false}
/>
);
}
}
(...)
이제 수정하기 기능은 거의 끝났습니다. 사용자가 헷갈리지 않기 위하여, EditorHeader 에서 isEdit 값이 true 라면, 작성하기가 아닌 수정하기 라는 문구를 보여주도록 설정하세요.
src/components/editor/EditorHeader/EditorHeader.js
(...)
const EditorHeader = ({onGoBack, onSubmit, isEdit}) => {
return (
(...)
<Button onClick={onSubmit} theme="outline">{isEdit ? '수정' : '작성'}하기</Button>
</div>
</div>
);
};
export default EditorHeader;
이제, 수정모드 일 때는 다음과 같이 수정하기 버튼이 나타납니다.

버튼의 문구가 잘 바뀌었나요? 내용을 수정하고 이 버튼을 눌러보세요. 변경된 내용을 가진 포스트 페이지로 이동 될 것입니다.
삭제 기능 구현하기
이 프로젝트에서의 마지막 포스트 관련 기능인, 삭제 기능을 구현해봅시다. 이 기능은, 포스트 페이지에서 수정 버튼의 우측에 있는 삭제 버튼을 클릭하면 발생하는데요, 삭제 버튼을 누른다고 해서 바로 삭제가 된다면 안되겠죠? 사용자가 실수로 누를 수도 있으니까요.
따라서, 삭제를 하기 전에 사용자에게 한번 더 물어보는 과정을 거치도록 하겠습니다.

ModalWrapper 와 AskRemoveModal 컴포넌트 생성
포스트 삭제 모달을 구현하기 위하여 우리는 먼저 ModalWrapper 라는 컴포넌트를 만들겠습니다. 이 컴포넌트는, state 가 있는 클래스형 컴포넌트로서, 전체화면에 불투명한 회색 배경을 깔아주고, 그 위에 흰색 박스를 보여주게 되는데, 이 과정에서 모달의 가시성 상태와, 전환 효과를 위한 상태를 관리하게 됩니다.
또한, 이 컴포넌트는 나중에 우리가 비밀번호 로그인을 구현하게 될 때에도, 로그인 모달을 만들 때 재사용 되기도 합니다.
components 디렉토리에 modal 디렉토리를 생성한 뒤, ModalWrapper 라는 컴포넌트를 생성하세요. 컴포넌트는 클래스형으로 만드세요.
src/components/modal/ModalWrapper/ModalWrapper.js
import React, { Component } from 'react';
import styles from './ModalWrapper.scss';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);
class ModalWrapper extends Component {
render() {
const { children } = this.props;
return (
<div>
<div className={cx('gray-background')}/>
<div className={cx('modal-wrapper')}>
<div className={cx('modal')}>
{ children }
</div>
</div>
</div>
);
}
}
export default ModalWrapper;
이 컴포넌트는 children props 를 받아와서 modal 엘리먼트 내부에서 보여줍니다. 상태관리는 나중에 하도록 하고, 지금은 컴포넌트의 틀부터 잡아주겠습니다.
src/components/modal/ModalWrapper/ModalWrapper.scss
@import 'utils';
.gray-background {
background: rgba(100,100,100,0.5);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
}
.modal-wrapper {
z-index: 10;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.modal {
@include material-shadow(3, 0.5);
background: white;
}
}
그 다음엔, 이 컴포넌트를 불러와서 사용 할 AskRemoveModal 컴포넌트를 modal 디렉토리에 생성하세요. 지금은 ModalWrapper 컴포넌트를 불러와서 children 부분엔 “내용” 이라는 텍스트를 넣어서 렌더링하세요.
src/components/modal/AskRemoveModal/AskRemoveModal.js
import React from 'react';
import styles from './AskRemoveModal.scss';
import classNames from 'classnames/bind';
import ModalWrapper from 'components/modal/ModalWrapper';
const cx = classNames.bind(styles);
const AskRemoveModal = () => (
<ModalWrapper>
내용
</ModalWrapper>
);
export default AskRemoveModal;
그 다음엔 AskRemoveModal 컴포넌트를 PostPage 에서 불러와서 렌더링하세요.
src/pages/PostPage.js
(...)
const PostPage = ({ match }) => {
const { id } = match.params;
return (
<PageTemplate>
<Post id={id}/>
<AskRemoveModal/>
</PageTemplate>
);
};
export default PostPage;
그러면, 이렇게 페이지에 페이지의 중앙에 내용이라고 적힌 흰색 박스가 나타나게 됩니다.

AskRemoveModal 컴포넌트 만들기
이제 이 모달에서 보여질 내용을 설정하겠습니다. 이 컴포넌트는 두가지 영역으로 나뉘어져있습니다. 윗 부분엔 모달의 제목, 내용이 있고, 하단에는 사용자가 선택을 할 수 있는 버튼이 있습니다.
src/components/modal/AskRemoveModal/AskRemoveModal.js
import React from 'react';
import styles from './AskRemoveModal.scss';
import classNames from 'classnames/bind';
import ModalWrapper from 'components/modal/ModalWrapper';
import Button from 'components/common/Button';
const cx = classNames.bind(styles);
const AskRemoveModal = () => (
<ModalWrapper>
<div className={cx('question')}>
<div className={cx('title')}>포스트 삭제</div>
<div className={cx('description')}>이 포스트를 정말로 삭제하시겠습니까?</div>
</div>
<div className={cx('options')}>
<Button theme="gray">취소</Button>
<Button>삭제</Button>
</div>
</ModalWrapper>
);
export default AskRemoveModal;
src/components/modal/AskRemoveModal/AskRemoveModal.scss
@import 'utils';
.question {
background: white;
padding: 2rem;
.title {
font-size: 1.25rem;
font-weight: 500;
}
.description {
margin-top: 0.25rem;
}
}
.options {
padding: 1rem;
background: $oc-gray-1;
text-align: right;
}
이렇게 코드를 작성하고 나면, 이전에 봤던 것 처럼 모달이 보여지게 됩니다.
모달 상태 관리하기
모달은, 기본 상태에선 보여지지 않고, 유저가 삭제 버튼을 눌렀을 때만 보여져야 합니다. 따라서, 우리는 이 컴포넌트가 어떤 상황에 보여져야 할 지 설정을 해주어야 합니다. 이를 구현하기 위해 우선 ModalWrapper 컴포넌트에서 visible 이란 props 를 받아와서 상황에 따라 null 을 구현하도록 렌더링 함수를 수정하세요.
src/components/modal/ModalWrapper/ModalWrapper.js
(...)
class ModalWrapper extends Component {
render() {
const { children, visible } = this.props;
if(!visible) return null;
(...)
이렇게 하고 나면, 컴포넌트가 visible 값이 true 일 때에만 보여지기 때문에 화면에 나타나지 않을 것 입니다.
그 다음에는, 모달의 가시성 상태를 관리하기 위하여 리덕스 모듈 중 base.js 를 수정하겠습니다. 이 모듈은, 모달의 가시성을 관리하게 되며, 추후 로그인 기능을 구현하게 될 때, 로그인 모달의 상태와, 로그인 상태도 관리하게 됩니다.
src/store/modules/base.js
import { createAction, handleActions } from 'redux-actions';
import { Map } from 'immutable';
import { pender } from 'redux-pender';
// action types
const SHOW_MODAL = 'base/SHOW_MODAL';
const HIDE_MODAL = 'base/HIDE_MODAL';
// action creators
export const showModal = createAction(SHOW_MODAL);
export const hideModal = createAction(HIDE_MODAL);
// initial state
const initialState = Map({
// 모달의 가시성 상태
modal: Map({
remove: false,
login: false // 추후 구현 될 로그인 모달
})
});
// reducer
export default handleActions({
[SHOW_MODAL]: (state, action) => {
const { payload: modalName } = action;
return state.setIn(['modal', modalName], true);
},
[HIDE_MODAL]: (state, action) => {
const { payload: modalName } = action;
return state.setIn(['modal', modalName], false);
}
}, initialState)
여기서 우리는 SHOW_MODAL 과 HIDE_MODAL 액션을 만들게 됩니다. 이 액션은, 주어진 payload 값에 따라서 modal Map 내부에 있는 값을 true 혹은 false 로 전환해줍니다.
이 과정에서, 굳이 액션을 두개로 나누지 않고, SET_MODAL_VISIBILITY 같은 액션을 만들어서 payload 부분엔 modalName 과 visible 값을 받아와서 구현해도 무방합니다. 어떠한 방식으로 하던, 여러분의 자유입니다.
리덕스 모듈을 만들었다면, 컨테이너 컴포넌트도 만들어주어야겠죠? containers 디렉토리에 modal 디렉토리를 만들고, 그 안에 AskRemoveModalContainer 컴포넌트를 생성하세요.
src/containers/modal/AskRemoveModalContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as baseActions from 'store/modules/base';
import * as postActions from 'store/modules/post';
import AskRemoveModal from 'components/modal/AskRemoveModal';
class AskRemoveModalContainer extends Component {
handleCancel = () => {
}
handleConfirm = () => {
}
render() {
const { visible } = this.props;
const { handleCancel, handleConfirm } = this;
return (
<AskRemoveModal visible={visible} onCancel={handleCancel} onConfirm={handleConfirm}/>
);
}
}
export default connect(
(state) => ({
visible: state.base.getIn(['modal', 'remove'])
}),
(dispatch) => ({
BaseActions: bindActionCreators(baseActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch)
})
)(AskRemoveModalContainer);
visible 값을 리덕스에서 받아와서 AskRemoveModal 에게 전달해주었으며, base 와 post 모듈의 액션들을 미리 연결해놓았습니다. 그리고, 확인 버튼을 눌렀을 때 실행되는 handleConfirm 과 취소를 눌렀을 때 실행되는 handleCancel 메소드에 비어있는 함수를 미리 설정해놓고, 이를 onConfirm / onCancel 이라는 이름으로 AskRemoveModal 에 전달해주었습니다.
그리고 이렇게 넣어준 props 를 AskRemoveModal 에서 반영시키세요.
src/components/modal/AskRemoveModal/AskRemoveModal.js
(...)
const AskRemoveModal = ({visible, onConfirm, onCancel}) => (
<ModalWrapper visible={visible}>
<div className={cx('question')}>
<div className={cx('title')}>포스트 삭제</div>
<div className={cx('description')}>이 포스트를 정말로 삭제하시겠습니까?</div>
</div>
<div className={cx('options')}>
<Button theme="gray" onClick={onCancel}>취소</Button>
<Button onClick={onConfirm}>삭제</Button>
</div>
</ModalWrapper>
);
export default AskRemoveModal;
그 다음엔, PostPage 에서 AskRemoveModal을 방금 만든 컨테이너 컴포넌트로 대체시키세요.
src/pages/PostPage.js
(...)
import AskRemoveModalContainer from 'containers/modal/AskRemoveModalContainer';
const PostPage = ({ match }) => {
const { id } = match.params;
return (
<PageTemplate>
<Post id={id}/>
<AskRemoveModalContainer/>
</PageTemplate>
);
};
export default PostPage;
모달을 보여줄 준비가 거의 끝났습니다. 이제 HeaderContainer 컴포넌트에서 만들어뒀던 handleRemove 메소드가 호출 될 때, 모달을 띄우도록 코드를 작성하세요.
src/containers/common/HeaderContainer.js
(...)
import * as baseActions from 'store/modules/base';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class HeaderContainer extends Component {
handleRemove = () => {
const { BaseActions } = this.props;
BaseActions.showModal('remove');
}
render() {
(...)
}
}
export default connect(
(state) => ({}),
(dispatch) => ({
BaseActions: bindActionCreators(baseActions, dispatch)
})
)(withRouter(HeaderContainer));
코드를 다 작성하셨다면, 포스트 페이지에서 삭제 버튼을 눌러보세요. 모달이 잘 뜨나요?
삭제 모달 버튼 기능 구현하기
삭제 모달에서 삭제 버튼을 누르면, 현재 보고있는 포스트를 삭제하고, 취소 버튼을 누르면 모달이 종료되도록 설정하겠습니다.
우선, 취소버튼 부터 구현해봅시다.
AskRemoveModalContainer 컴포넌트의 handleCancel 메소드에서 BaseActions.hideModal 를 호출하세요.
src/containers/modal/AskRemoveModalContainer.js - handleCancel
handleCancel = () => {
const { BaseActions } = this.props;
BaseActions.hideModal('remove');
}
그 다음엔, api.js 파일에 포스트 삭제 API 함수를 생성하고, 이를 위한 액션을 준비하고 나서 handleConfirm 에서 호출하세요.
src/lib/api.js
(...)
export const removePost = (id) => axios.delete(`/api/posts/${id}`);
src/store/modules/post.js
(...)
// action types
const GET_POST = 'post/GET_POST';
const REMOVE_POST = 'post/REMOVE_POST';
// action creators
export const getPost = createAction(GET_POST, api.getPost);
export const removePost = createAction(REMOVE_POST, api.removePost);
(...)
삭제 액션은 post 모듈에서 작성하면 되며, 리듀서에서 상태관리는 따로 해주지 않아도 됩니다.
액션을 작성 한 후엔, AskRemoveModalContainer 를 withRouter 로 감싸서 handleConfirm 에서 방금 만든 액션 생성 함수에 현재 포스트 아이디를 파라미터로 넣어서 호출하고, 삭제요청이 완료 된 후 홈페이지로 주소를 이동시키세요.
src/containers/modal/AskRemoveModalContainer.js
(...)
import { withRouter } from 'react-router-dom';
class AskRemoveModalContainer extends Component {
(...)
handleConfirm = async () => {
const { BaseActions, PostActions, history, match } = this.props;
const { id } = match.params;
try {
// 포스트 삭제 후, 모달 닫고 홈페이지로 이동
await PostActions.removePost(id);
BaseActions.hideModal('remove');
history.push('/');
} catch (e) {
console.log(e);
}
}
(...)
}
export default connect(
(state) => ({
visible: state.base.getIn(['modal', 'remove'])
}),
(dispatch) => ({
BaseActions: bindActionCreators(baseActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch)
})
)(withRouter(AskRemoveModalContainer));
자, 여기까지 하고나면 삭제 기능이 완료됩니다. 한번 포스트 삭제를 시도해보세요. 홈 화면으로 이동하고, 기존에 작성했던 포스트가 사라졌나요?
모달 전환 효과 구현하기
모달이 나타나고 사라질 때, 좀 더 자연스럽게 보여지기 위하여 전환 애니메이션 효과를 설정해봅시다. 전환 효과를 만들 때에는, CSS 의 @keyframes 를 이용하여 구현합니다. @keyframes 를 사용하여 전환 효과의 시작 부분, 그리고 마지막 부분의 스타일을 지정해주면 스타일이 서서히 변화하게 되면서 애니메이션 효과가 구현됩니다.
우리는 ModalWrapper.scss 에 총 4가지 종류의 @keyframes 를 만들겠습니다.
- fadeIn: 투명도가 0% 100%
- fadeout: 투명도가 100% 0%
- slideUp: 아래에서 위로 올라오는 효과
- slideDown: 위에서 아래로 내려가는 효과
ModalWrapper.scss 파일의 상단에 다음 코드를 추가하세요:
src/components/modal/ModalWrapper/ModalWrapper.scss
@import 'utils';
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes slideUp {
0% { transform: translateY(50vh); }
100% { transform: translateY(0); }
}
@keyframes slideDown {
0% { transform: translateY(0); }
100% { transform: translateY(50vh); }
}
(...)
그 다음에는, gray-background 클래스와 modal 클래스 내부에 &.enter 와 &.leave 클래스를 만들어서 각각 알맞는 animation 을 설정하세요.
src/components/modal/ModalWrapper/ModalWrapper.scss
(...)
.gray-background {
(...)
&.enter {
animation: fadeIn 0.25s ease-in both;
}
&.leave {
animation: fadeOut 0.25s ease-in both;
}
}
.modal-wrapper {
(...)
.modal {
(...)
&.enter {
animation: slideUp 0.25s ease-in both;
}
&.leave {
animation: slideDown 0.25s ease-in both;
}
}
}
애니메이션은 0.25 초동안 지속되도록 설정했습니다. 자, 이에 맞춰서 ModalWrapper 컴포넌트에서 visible 값이 바뀜에 따라 내부 state 를 설정하여 enter 혹은 leave 애니메이션을 적용시켜보세요.
src/components/modal/ModalWrapper/ModalWrapper.js
(...)
class ModalWrapper extends Component {
state = {
animate: false
}
startAnimation = () => {
// animate 값을 true 로 설정 후
this.setState({
animate: true
});
// 250ms 이후 다시 false 로 설정
setTimeout(() => {
this.setState({
animate: false
});
}, 250)
}
componentDidUpdate(prevProps, prevState) {
if(prevProps.visible !== this.props.visible) {
this.startAnimation();
}
}
render() {
const { children, visible } = this.props;
const { animate } = this.state;
// visible 값과 animate 값이 둘 다 false 일 떄에만
// null 을 리턴합니다.
if(!visible && !animate) return null;
// 상태에 따라 애니메이션 설정
const animation = animate && (visible ? 'enter' : 'leave');
return (
<div>
<div className={cx('gray-background', animation)}/>
<div className={cx('modal-wrapper')}>
<div className={cx('modal', animation)}>
{ children }
</div>
</div>
</div>
);
}
}
export default ModalWrapper;
이 과정에서, startAnimation 메소드를 만들고, componentDidUpdate 에서 visible 값이 바뀔 때 마다 이 메소드를 호출하도록 설정합니다. startAnimation 에서는, 호출 시 내부 state 인 animate 값을 true 로 설정하고, 250ms 초 뒤 false 로 다시 설정합니다.
animate 가 true 일 때에는, visible 값에 따라서 ‘enter’ 혹은 ‘leave’ 를 배경화면과 모달에 클래스로 넣어주며, 애니메이션이 진행되고 있는 동안에는 컴포넌트가 화면에서 사라지지 않도록 visible 값과 animate 값이 둘다 false 일 때에만 null 을 리턴하도록 설정 되었습니다.
자, 이제 삭제 모달의 애니메이션 구현 까지 끝났습니다. 이제 프로젝트 기능 구현의 마지막 단계인, 관리자 로그인 인증을 구현해볼 차례입니다.