파이널 프로젝트를 진행하면서 기존처럼 모든 메세지를 alert()
로 처리 하지 않고 더 나은 UI/UX를 위해 모달과 토스트 메세지를 활용하기로 했다.
기본적으로 토스트 메세지는 toastify 라이브러리를 사용했지만, 특정 시간 이후 사라지는 모달이 필요할 땐 토스트 모달을 따로 만들어서 사용했다.
이제 alert 메세지보다 더 좋은 메세지를 만들 수 있다!
index.html
의 #root div 아래 modal
이 표시되는 또 다른 div를 두게된다.
// index.html에 새로운 div를 생성한다.
<body>
<div id="root"></div>
<div id="modal"></div>
</body>
// modal div에 자식 컴포넌트를 출력하는 modal portal 컴포넌트를 만든다.
import reactDom from 'react-dom';
const ModalPortal = ({ children }) => {
const element = document.getElementById('modal');
// 1. element의 id가 'modal'인 html element를 element라는 변수에 담는다.
return reactDom.createPortal(children, element);
// 2. 해당 element에 modal portal과 children 요소가 함께 생성되도록 한다.
// 2-1. createPortal() 메서드로 인자로 넘긴 컴포넌트를 렌더링할 수 있다.
// 2-2. 이때 인자는 렌더링할 컴포넌트와 타겟노드 2개이다.
};
export default ModalPortal;
function Modal({ onClose, content }) {
// 1. 모달이 닫히도록 onClose와 모달의 내용을 될 content를 props로 가져온다.
return (
<ModalPortal>
// 2. modal portal 아래 위치하도록 한다.
<StBackground onClick={onClose}>
// 3. 외부 영역 클릭 시, 모달이 닫히도록 설정
<StModalBorder
role="presentation"
onClick={(event) => {
event.stopPropagation();
}}
// 4. 해당 부분도 모달의 내용이 아니다보니, 레이아웃 부분을 클릳해도 닫힐 수 있다.
// 4-1. 그런 문제를 방지하기 위해 모달 레이아웃 컴포넌트 클릭 시 event.stopPropagation을 심어서 이벤트 버블링을 방지한다.
>
<StCloseBtn onClick={onClose}>
// 5. 모달에 [X] 버튼을 하나둬서 클릭할 경우 닫히도록 한다.
<img src={closeBtn} alt="방 닫기" />
</StCloseBtn>
<div>{content}</div>
// 6. 여러가지 모달을 만들 때, 매번 다른 내용을 보여주기 위해 content를 따로 설정한다.
</StModalBorder>
</StBackground>
</ModalPortal>
);
}
function CreateRoomModal() {
//////// 코드 생략 ////////
return (
<StModalContainer onKeyUp={onKeyUpEnter}>
<StTitle>방 만들기</StTitle>
<Input
placeholder="방 제목은 7글자 이하로 입력 가능하닭"
value={gameRoomName}
onChange={(e) => {
setGameRoomName(e.target.value);
setInputCount(e.target.value.length);
}}
maxLength={7}
/>
<StLimit>{inputCount}/7</StLimit>
<button onClick={onClickRoomCreate}>
<img src={modalCreateBtn} alt="방 만들기" />
</button>
</StModalContainer>
);
}
app.js
에 하위 컴포넌트를 두는것 보다 나은것 같아서 portal을 사용했다.<div>
{isStartModal && (
// 1. isStartModal의 값이 true일 때 모달이 나타나고 false일 땐 나타나지 않는다.
<ToastMessage setToastState={setIsStartModal} type="start" />
// 1-1. setToastState라는 이름으로 setIsStartModal 함수를 보내서 자식 컴포넌트에서도 state를 변경할 수 있도록 한다.
// 1-2. 이때 type이라는 특정값도 함께 보내서 어떤 형태의 토스트 모달을 띄워야할지 알려준다.
)}
{isEndGameModal && (
// 2. isEndGameModal의 값이 true일 때 모달이 나타나고 false일 땐 나타나지 않는다.
<ToastMessage setToastState={setIsEndGameModal}
type="end" />
// 2-1. setToastState라는 이름으로 setIsEndModal 함수를 보내서 자식 컴포넌트에서도 state를 변경할 수 있도록 한다.
// 2-2. 해당 모달의 자식 컴포넌트 (content)가 받을 type값을 end로 변경해서 보낸다.
)}
</div>
function Toast({ setToastState, type }) {
useEffect(() => {
// 1. useEffect를 사용해서 컴포넌트가 처음 렌더링 됐을 때 아래 내용이 실행된다.
const timer = setTimeout(() => {
setToastState(false);
// 2. setTimeout 함수를 이용해서 3초 후 setToastState의 값이 false가 되도록 만든다.
}, 3000);
return () => {
clearTimeout(timer);
// 3. 그리고 실행됐던 setTimeout 함수를 없애는 clearTimeout 함수를 이용한다.
};
}, []);
return (
<StBackground>
<StToastBorder>
{type === 'start' ?
// 4. props로 받아온 type의 값이 'start' 일 때,
<StToastMessage>
<img src={gameStart} alt="게임 시작" />
// 4-1. 게임 시작 이미지가 보이게 만들고,
</StToastMessage>
) : (
// 4-2. type이 'start'가 아닐 경우,
<StToastAnswer>
<img src={gameAnswer} alt="게임 끝" />
// 4-3. 게임 종료 이미지가 보이게 만든다.
</StToastAnswer>
)}
</StToastBorder>
</StBackground>
);
}
alert()
를 사용해서 사용자에게 경고 문구를 날렸었지만, react-toastify
라는 라이브러리를 알게되어서 처음으로 토스트 메세지를 만들어봤다.import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const useToast = (message, type) => {
// 1. 커스텀 훅을 import 했을 때 실행될 함수를 만든다.
// 1-1. 해당 함수의 매개변수로 message와 type을 지정한다.
const config = {
// 2. config 값을 설정해서 기본 커스터마이징을 한다.
position: 'top-center',
// 2-1. 위치: 위쪽 중간
autoClose: 2000,
// 2-2. 2초 후 사라짐
hideProgressBar: true,
// 2-3. 사라지기까지 progressBar 보이지 않게 설정
closeOnClick: true,
// 2-4. 클릭할 경우 토스트 메세지 사라짐
rtl: false,
// 2-5. 알림 좌우 반전 안 함
pauseOnFocusLoss: false,
// 2-6. 화면 벗어나도 알람 정지 안함
draggable: false,
// 2-7. 드래그 불가능
pauseOnHover: false,
// 2-8. 마우스 올리면 알람 정지하지 않음
};
switch (type) {
// 3. type 설정 시, 해당 type에 맞춰 switch case가 걸리고, 해당하는 case의 토스트 메세지가 생성된다.
case 'success':
return toast.success(message, config);
case 'error':
return toast.error(message, config);
case 'warning':
return toast.warning(message, config);
default:
return toast(message, config);
}
// 3-1. 성공, 실패, 경고, default 케이스마다 토스트 메세지의 마크가 다르게 표시된다.
};
export default useToast;
config
값을 설정해서 나름 커스터마이징도 가능했다!이름 | 기능 | 세부 옵션 | 기본 값 |
---|---|---|---|
position | 알람 위치 지정 | "top-left", "top-right", "top-center", "bottom-left", "bottom-right", "bottom-center" | "top-right" |
autoClose | 자동 off 시간 | 1 이상의 숫자 ( 1000 = 1초 ) | {3000} |
hideProgressBar | 진행 시간 바 숨김 | {true} (숨김), {false} (표시) | {false} |
newestOnTop | 새로운 알람 위치 설정 | {true} (위쪽), {false} (아래쪽) | {false} |
closeOnClick | 클릭으로 알람 off | {true} (끄기), {false} (끄지 않기) | {false} |
rtl | 알람 좌우 반전 | {true} (반전), {false} (반전 안함) | {false} |
pauseOnFocusLoss | 화면 벗어나면 알람 정지 | {true} (정지), {false} (정지 안함) | {true} |
draggable | 드래그 가능 | {true} (가능), {false} (불가능) | {false} |
pauseOnHover | 마우스 올리면 알람 정지 | {true} (정지), {false} (정지 안함) | {true} |
transition | 알람 애니메이션 지정 | Slide, Bounce, Zoom, Flip ( 모듈 import 필요 ) | {Bounce} |
limit | 알람 개수 제한 | 1 이상의 숫자 ( 1 = 1개 ) | 제한 없음 |
if (comment === '') {
// 1. 만약 코멘트의 내용이 비어있을 경우,
useToast('댓글 내용이 없닭!', 'warning');
// 1-1. 해당 문구가 써져있는 토스트 메세지가 표시된다.
// 1-2. type은 warning이기 떄문에 경고 마크가 표시된다.
return;
}
사소하다면 사소한 내용이지만, 조금 더 다양한 구성의 웹사이트를 만들 수 있게 되었다.
실전 프로젝트인만큼 조금 더 신경쓰고 싶고, 실제 있는 서비스처럼 보였으면 한다.
이제 약 한달의 시간이 남았는데, 남은 시간도 무사히 지나갔으면 좋겠다!
출처: