Toast 컴포넌트 사용기

윤뿔소·2023년 11월 19일
1

이모저모

목록 보기
8/12

웹 개발을 하면서 Toast 컴포넌트를 개발해달라는 요구사항이 들어왔다. 그래서 그 구현기를 간단히 작성해보려고 한다.

참고로 리액트 관련 Toast는 사실 react-toastify를 쓰면 정말 쉬운데, 외부 라이브러리의 힘을 빌리지 않고 작성해보았다.

사용한 라이브러리

디자인, 애니메이션 등등을 만들기엔 좀 오래 걸려서 UI 부분으로는 MUI를 쓰기로 했다.

MUI의 SnackBar로 Toast UI를 만들었고, 애니메이션은 Slide Transition을 사용했다.

Toast UI 만들기

일단 로직을 제외한, 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를 추가하자.

  1. forwardRef<HTMLDivElement, AlertProps>를 통해 forwardRef를 사용하여 Alert 컴포넌트를 정의.
    Alert는 외부에서 전달된 ref를 받아 MuiAlert 컴포넌트에 전달함.
  2. variant 속성을 'filled'로,elevation은 그림자 효과를 조절.
  3. MuiAlertseverity로 성공, 실패, 경고, 정보 상태를 나타냄.

이제 그렇게 만들면 아래 이미지처럼 나올 것이다.

업로드중..

variant는 위 이미지의 UI 색 채움을 조절하는 기능이다.

참고: forwardRef란?

forwardRef는 Ref를 부모 컴포넌트에서 자식 컴포넌트로 전달할 때 사용되는 React 기능이다.
함수 컴포넌트에서 Ref(DOM)를 사용하고자 할 때, forwardRef를 통해 Ref를 자식 컴포넌트에 전달할 수 있다.
주로 외부에서 컴포넌트의 내부 요소에 접근해야 하는 경우에 활용된다.

즉, useRefRef-DOM 관리와 비슷한데, 함수 컴포넌트 내에서 DOM을 관리하고 싶다면 useRef, 직접 함수 컴포넌트로 만들어 Ref를 전달하고 관리하고, 자식에게 전달하고 싶으면 forwardRef로 컴포넌트를 만들어 전달할 때 쓰는 것이다.

Alert 컴포넌트에 forwardRef가 사용된 이유는 MuiAlert를 래핑하고 있고, Snackbar 컴포넌트 내에서 사용될 때 ref를 전달하기 위해서이다.
forwardRef를 사용함으로써 Alert 컴포넌트가 Snackbar와 함께 사용될 때 MuiAlert의 DOM 요소에 외부에서 Ref를 전달하고 관리할 수 있게 됐다.

애니메이션 넣기

되게 쉽다. Slide Transition을 가져와 SnackbarTransitionComponent속성에 넣어주면 된다.

...
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 로직 짜기

로직을 짜서 나는 커스텀 훅을 만들었다.

필요한 재료가 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 };
}
  1. openToast 상태
    • 토스트 메시지의 열림/닫힘 상태를 나타내는 불린데이터.
    • 초기값은 false로 설정되어 있음.
  2. severity 상태
    • 토스트 메시지의 UI를 조절하는 문자열. 가능한 값은 'success', 'error', 'warning', 'info' 중 하나.
  3. messageToast 상태
    • 토스트 메시지에 표시할 텍스트를 나타내는 문자열.
  4. showToast 함수
    • 토스트 메시지를 표시하기 위한 함수.
    • 두 개의 인수: newSeverity (토스트 메시지의 severity)와 newMessage (토스트 메시지의 텍스트).
    • showToast 함수는 받은 매개변수를 사용해 severity, messageToast, 그리고 openToast 상태를 업데이트. 실행하면 토스트 메시지가 열리게 됨.
    • useCallback 훅을 사용해 함수가 렌더링될 때마다 새로 생성되지 않도록 최적화.
  5. 훅의 반환값
    • 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와 훅을 삽입해주면 좋다.

profile
코뿔소처럼 저돌적으로

4개의 댓글

comment-user-thumbnail
2023년 11월 19일

오 커스텀 훅으로 만들 생각을 왜 안했죠 전 ..!

답글 달기
comment-user-thumbnail
2023년 11월 22일

저도 이번에 toast 기능을 사용할 일이 있었는데 라이브러리로 사용햇어서 되게 유익하게 읽고 갑니당 다음에는 저도 커스텀훅으로 만들어봐야겠어요 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 11월 26일

개인적으로 프로젝트를 진행했을 때 동일하게 MUI의 Snackbar 컴포넌트를 활용한 경험이 있는데 다시 한 번 복습하고 갑니다! Alert 컴포넌트는 사용하지 못했던 거 같은데 공부하고 갑니당!

답글 달기
comment-user-thumbnail
2023년 11월 26일

맨날 라이브러리만 쓸생각만 햇는데 ㅎㅎ mui 무겁긴한데 커스텀하기도 좋고 디자인도 깔끔해서 쓰기 편하더라구옇ㅎ 잘보고 갑니당!

답글 달기