forked from codesquad-members-2023/second-hand-max
-
Notifications
You must be signed in to change notification settings - Fork 2
Toast 메시지 상태 관리를 위한 컴포넌트와 훅 구현하기
유지수 Jisoo Yoo edited this page Dec 6, 2023
·
9 revisions
- API 호출 성공/실패 등의 상황에서 유저가 수행한 액션에 대해 일관성 있는 피드백 제공 필요
- Toast 메시지 상태 관리를 위한 Store, Toast/Toaster 컴포넌트 및 useToast hook을 직접 구현
📌 Code
export const useToasterAtom = atom(
(get) => get(toasterAtom).toasts,
(get, set, { type, payload }: ToasterAction) => {
const prevAtom = get(toasterAtom);
switch (type) {
case "add":
set(toasterAtom, {
toasts: [...prevAtom.toasts, { ...payload, id: prevAtom.index + 1 }],
index: prevAtom.index + 1,
});
break;
case "remove":
set(toasterAtom, {
toasts: prevAtom.toasts.filter((toast) => toast.id !== payload.id),
index: prevAtom.index + 1,
});
break;
}
}
);
📌 Code
export default function Toaster() {
const toasts = useAtomValue(useToasterAtom);
return (
<StyledToaster>
{toasts.map((toast: ToasterType) => (
<Toast key={toast.id} {...toast} />
))}
</StyledToaster>
);
}
function Toast({ id, type, title, message }: ToasterType) {
const setToaster = useSetAtom(useToasterAtom);
const [opacity, setOpacity] = useState(0);
useEffect(() => {
setOpacity(1);
const removeTimeout = setTimeout(() => {
setToaster({ type: "remove", payload: { id } });
}, TOAST_DURATION);
return () => {
clearTimeout(removeTimeout);
};
}, [id, setToaster]);
useEffect(() => {
const visibilityTimeout = setTimeout(() => {
setOpacity(0);
}, TOAST_DURATION - ANIMATION_DURATION);
return () => {
clearTimeout(visibilityTimeout);
};
}, []);
return (
<StyledToast
$opacity={opacity}
$animationDirection={opacity === 1 ? "enter" : "exit"}>
<div className="toast-container">
<ToastIcon type={type} />
<ToastText>
<span className="toast-title">{title}</span>
<span className="toast-message">{message}</span>
</ToastText>
</div>
</StyledToast>
);
}
📌 Code
import { useSetAtom } from "jotai";
import { useCallback } from "react";
import { ToasterInfo, useToasterAtom } from "store/toaster";
export const useToast = () => {
const setToaster = useSetAtom(useToasterAtom);
const toast = useCallback(
(toasterInfo: ToasterInfo) =>
setToaster({ type: "add", payload: { ...toasterInfo } }),
[setToaster]
);
return {
toast,
};
};
- 컴포넌트 라이프사이클에 따라 FadeIn, FadeOut keyframes를 추가
📌 Code
const TOAST_DURATION = 3000;
const ANIMATION_DURATION = 1000;
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(50%);
}
to {
opacity: 1;
transform: translateY(0%);
}
`;
const fadeOut = keyframes`
from {
opacity: 1;
transform: translateY(0%);
}
to {
opacity: 0;
transform: translateY(50%);
}
`;
const StyledToast = styled.div<{
$opacity: number;
$animationDirection: "enter" | "exit";
}>`
opacity: ${({ $opacity }) => $opacity};
transition: all ${ANIMATION_DURATION}ms ease-in-out;
animation: ${({ $animationDirection }) =>
$animationDirection === "enter" ? fadeIn : fadeOut}
${ANIMATION_DURATION}ms ease-in-out;
`;