웹 개발을 하면서 Toast 컴포넌트를 개발해달라는 요구사항이 들어왔다. 그래서 그 구현기를 간단히 작성해보려고 한다.
참고로 리액트 관련 Toast는 사실 react-toastify를 쓰면 정말 쉬운데, 외부 라이브러리의 힘을 빌리지 않고 작성해보았다.
디자인, 애니메이션 등등을 만들기엔 좀 오래 걸려서 UI 부분으로는 MUI를 쓰기로 했다.
MUI의 SnackBar로 Toast UI를 만들었고, 애니메이션은 Slide Transition을 사용했다.
일단 로직을 제외한, UI를 먼저 만들어보겠다. MUI 공식문서를 참고해 만들었다.
SnackBar Import 및 만들기먼저 나는 SnackBar 디자인이 가장 Toast에 맞다고 생각해 SnackBar를 썼다.
// src/layouts/toast/index.tsx
interface ToastProps {
open: boolean;
severity: 'success' | 'error' | 'warning' | 'info';
message: string;
closeToast: () => void;
}
export default function Toast({
open,
message,
closeToast,
}: ToastProps) {
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={open}
autoHideDuration={2500}
onClose={closeToast}
message={message}
/>
);
}
Snackbar만 이용한다면 이렇게 사용하면 된다.
못생겼다.
하지만 이렇게 했을 시 디자인 커스텀 같은 것이 미리 세팅된 거로 못하기 때문에 여기에 이제 Alert 컴포넌트를 넣고 미리 설정된 명령어를 넣으면 우리가 원하는 디자인이 나온다.
Alert 넣기import Snackbar from '@mui/material/Snackbar';
import MuiAlert, { AlertProps } from '@mui/material/Alert';
import { forwardRef } from 'react';
const Alert = forwardRef<HTMLDivElement, AlertProps>(
function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant='filled' {...props} />;
},
);
interface ToastProps {
open: boolean;
severity: 'success' | 'error' | 'warning' | 'info';
message: string;
closeToast: () => void;
}
export default function Toast({
open,
severity,
message,
closeToast,
}: ToastProps) {
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={open}
autoHideDuration={2500}
onClose={closeToast}
>
<Alert
severity={severity}
sx={{ width: '100%', fontSize: 15, fontWeight: 600 }}
>
{message}
</Alert>
</Snackbar>
);
}
이제 상태값에 따른 예쁜 디자인을 렌더링 하기 위해 MuiAlert를 추가하자.
forwardRef<HTMLDivElement, AlertProps>를 통해 forwardRef를 사용하여 Alert 컴포넌트를 정의.Alert는 외부에서 전달된 ref를 받아 MuiAlert 컴포넌트에 전달함.variant 속성을 'filled'로,elevation은 그림자 효과를 조절.MuiAlert의 severity로 성공, 실패, 경고, 정보 상태를 나타냄.이제 그렇게 만들면 아래 이미지처럼 나올 것이다.
variant는 위 이미지의 UI 색 채움을 조절하는 기능이다.
forwardRef는 Ref를 부모 컴포넌트에서 자식 컴포넌트로 전달할 때 사용되는 React 기능이다.
함수 컴포넌트에서 Ref(DOM)를 사용하고자 할 때, forwardRef를 통해 Ref를 자식 컴포넌트에 전달할 수 있다.
주로 외부에서 컴포넌트의 내부 요소에 접근해야 하는 경우에 활용된다.
즉, useRef랑 Ref-DOM 관리와 비슷한데, 함수 컴포넌트 내에서 DOM을 관리하고 싶다면 useRef, 직접 함수 컴포넌트로 만들어 Ref를 전달하고 관리하고, 자식에게 전달하고 싶으면 forwardRef로 컴포넌트를 만들어 전달할 때 쓰는 것이다.
위 Alert 컴포넌트에 forwardRef가 사용된 이유는 MuiAlert를 래핑하고 있고, Snackbar 컴포넌트 내에서 사용될 때 ref를 전달하기 위해서이다.
forwardRef를 사용함으로써 Alert 컴포넌트가 Snackbar와 함께 사용될 때 MuiAlert의 DOM 요소에 외부에서 Ref를 전달하고 관리할 수 있게 됐다.
되게 쉽다. Slide Transition을 가져와 Snackbar의 TransitionComponent속성에 넣어주면 된다.
...
import { Slide, SlideProps } from '@mui/material';
...
function SlideTransition(props: SlideProps) {
return <Slide {...props} direction='up' />;
}
export default function Toast({
open,
severity,
message,
closeToast,
}: ToastProps) {
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={open}
autoHideDuration={2500}
onClose={closeToast}
TransitionComponent={SlideTransition} // 여기!
>
<Alert
severity={severity}
sx={{ width: '100%', fontSize: 15, fontWeight: 600 }}
>
{message}
</Alert>
</Snackbar>
);
}
이렇게 한다면 슬라이드 애니메이션이 추가된다.
로직을 짜서 나는 커스텀 훅을 만들었다.
필요한 재료가 Toast가 나왔는지 안나왔는지 상태, MuiAlert의 상태(severity)를 조절하여 어떤 UI를 가졌는지 조절할 상태, Toast에 나올 메세지 string, 이 모든 상태를 실행시킬 명령어가 필요하다.
// src/hooks/use-toast.ts
import { useCallback, useState } from 'react';
export default function useToast() {
// Toast
const [openToast, setOpenToast] = useState(false);
const [severity, setSeverity] = useState<
'success' | 'error' | 'warning' | 'info'
>('success');
const [messageToast, setMessageToast] = useState('');
const showToast = useCallback(
(
newSeverity: 'success' | 'error' | 'warning' | 'info',
newMessage: string,
) => {
setSeverity(newSeverity);
setMessageToast(newMessage);
setOpenToast(true);
},
[],
);
return { openToast, severity, messageToast, showToast };
}
openToast 상태false로 설정되어 있음.severity 상태messageToast 상태showToast 함수newSeverity (토스트 메시지의 severity)와 newMessage (토스트 메시지의 텍스트).showToast 함수는 받은 매개변수를 사용해 severity, messageToast, 그리고 openToast 상태를 업데이트. 실행하면 토스트 메시지가 열리게 됨.useCallback 훅을 사용해 함수가 렌더링될 때마다 새로 생성되지 않도록 최적화.useToast 훅은 openToast, severity, messageToast, closeToast, showToast를 속성으로 가지는 객체를 반환.이를 통해 해당 훅을 사용하는 컴포넌트에서 토스트 메시지의 상태와 조작 함수들을 사용할 수 있다.
이제 만든 UI와 로직을 짠 훅을 합쳐 사용하면 된다.
...
onSuccess: () => {
queryClient.invalidateQueries(['orderList', vendorId]);
showToast('success', '메모를 추가했습니다.');
},
...
// Toast 훅
const { openToast, severity, messageToast, closeToast, showToast } = useToast();
...
return(
<Toast
open={openToast}
severity={severity}
message={messageToast}
closeToast={closeToast}
/>
위처럼 사용하여 토스트가 나올 곳에 showToast를 호출해 상태와 message를 넣고, 나올 UI와 훅을 삽입해주면 좋다.
오 커스텀 훅으로 만들 생각을 왜 안했죠 전 ..!