웹 개발을 하면서 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와 훅을 삽입해주면 좋다.
오 커스텀 훅으로 만들 생각을 왜 안했죠 전 ..!