상세 컨텐츠

본문 제목

토스트 메세지 전역 컴포넌트로 관리하기

프론트엔드

by pineSol 2024. 8. 2. 16:18

본문

안녕하세요. 디스턴스의 프론트엔드 개발자 박솔미입니다.

이전에 모달 컴포넌트를 전역 상태로 리팩토링 한 주영님의 포스팅에 이어

이번에는 ‘토스트 메세지’를 전역 컴포넌트로 관리하는 리팩토링 방식에 대해 설명드리려고 합니다.

 

모달 컴포넌트 리팩토리 기록 : https://dis-tance.tistory.com/3

 

리액트에서 전역 상태로 모달 관리하기

안녕하세요.팀 디스턴스의 프론트엔드 개발자 노주영입니다. 디스턴스 앱에서는 사용자가 한 화면에서 여러 가지 작업을 처리할 수 있게 모달 컴포넌트를 사용합니다. 특히 모바일을 기준으로

dis-tance.tistory.com

 


이전에 구현한 코드가 가지고 있던 문제점

토스트 메세지는 사용자에게 간단한 알림이나 경고 문구를 효과적으로 전달하는 방법으로,

서비스의 다양한 곳에서 편리하게 사용되고 있습니다.

저희는 ‘react-hot-toast’라이브러리를 활용하여 이 토스트 메세지를 구현하였습니다

 

기능 자체는 잘 작동하는 듯 하지만 리팩토링을 하는 과정에서

다음과 같은 문제점들을 발견하였습니다.

 

1. 컴포넌트마다 별도로 구현한 토스트 메세지 기능

import toast, { Toaster } from 'react-hot-toast';

const 컴포넌트 = () => {
	//...생략
	toast.error('방 정보를 가져오는데 실패했어요!')
	//...생략
	
	return (
			<Toaster
	      position="bottom-center"
	      toastOptions={{
	        style: {
	          fontSize: '14px',
	        },
	      }}
	      containerStyle={{
	        bottom: 104,
	      }}
	    />
	    //컴포넌트 내용
	)
}

토스트 메세지를 새로 도입할 때마다 위와 같은 코드가 컴포넌트에 추가됩니다.

이는 불필요한 코드 반복과 매번 라이브러리를 import 해와야하는 번거로움을 초래합니다.

게다가 토스트 관련 코드가 컴포넌트 코드 전체에 분산되어 있어 보기에도 불편합니다.

 

2. 구현 방식의 일관성

이전에 토스트 메세지를 사용하는 유형은 크게 2가지로 나뉩니다.

하나는 성공 혹은 에러(경고)의 메세지만 띄우는 경우이고

나머지 하나는 전송, 로딩의 과정이 들어간 메세지를 띄우는 경우입니다.

 

문제는 두 번째 유형의 토스트 메세지 구현 방식이 일관되지 않다는 점이었습니다.

아래 코드를 보면 같은 기능이지만 toast.promise로 구현한 코드가 있고,

id 값으로 loading, success, error를 따로 구현한 방식의 코드가 있습니다.

 

코드의 일관성을 위해서 이 부분도 수정이 필요했습니다.

//toast.promise 방식
toast.promise(response, {
  loading: '전송 중...',
  success: () => {
    //성공 시 수행 코드
    return '인증메일이 전송되었습니다.';
  },
  error: (err) => {
	  //에러 시 수행 코드
	  return '인증을 다시 시도해주세요.';
  },
});

//toast.id를 사용한 방식
const toastId = toast.loading('전송 중...');
try {
  //api 통신
  toast.success('인증번호가 전송되었습니다.', {
    id: toastId,
  });
} catch (error) {
  //에러 시 수행 코드
  toast.error('인증을 다시 시도해주세요.', { id: toastId });
}

 

 

3. 홈 화면에서 알림, 위치 설정에 대한 토스트 메세지 2개가 쌓이면서 화면의 UI를 일부 가리는 문제점

디스턴스는 위치 기반의 매칭과 랜덤 채팅 기능을 제공하기 때문에

GPS 설정과 알림 설정은 사용자 서비스 경험에 매우 중요한 영향을 미칠 수 있습니다.

 

따라서 서비스를 처음 들어왔을 때 홈 화면에서 알림 설정 여부, 위치 설정 여부에 따라

각각 2개의 토스트 메세지를 띄워 사용자에게 알림을 주었고

‘해결하기’ 버튼을 누르면 각각의 해결 방법 페이지로 리다이렉션 하도록 구현해 놓았습니다.

수정 전 화면 플로우

하지만 홈 화면에서 2개의 토스트 메세지가 쌓이는 것은

홈 화면 UI를 가리고 사용자에게 답답함을 주는 문제점이 있었습니다.

 

실제로 상반기 서비스 운영 동안 구글폼을 통해 사용자에게 피드백을 받았을 때

홈 화면 오른쪽 하단에 새로고침 버튼이 토스트 메세지에 가려지는 점을 수정해달라는 피드백을 받았었습니다.

 

그 때는 토스트 메세지 크기를 줄이는 방향으로 임시조치를 해놨었는데

이번 기회에 리팩토링을 하면서 해당 부분을 좀 더 나은 방향으로 수정하고자 합니다.

 

위와 같은 문제점들로 인하여 토스트 메세지 구현 방식을 통일하고
여러 개의 토스트를 하나의 전역 컴포넌트로 관리하는 방식으로 구현해보려고 합니다.

 

 

 

리팩토링을 위한 요구사항 정리

  1. 하나의 전역 컴포넌트로 토스트를 관리한다.
  2. 일관된 토스트 사용법을 만든다.
  3. 홈 화면을 가리는 2개의 토스트 메세지 문제를 해결한다.

 

구체적인 리팩토링 방식

먼저 요구사항 1번의 하나의 전역 컨포넌트를 만든 방식은 다음과 같습니다.

providers 폴더에 Toaster를 반환하는 GlobalToastContainer.jsx라는 컴포넌트를 만듭니다.

import { Toaster } from 'react-hot-toast';

const GlobalToastContainer = () => {
    return (
      <Toaster
        containerStyle={{
          bottom: 104,
        }}
        toastOptions={{
          style: {
            fontSize: '14px',
          },
        }}
      />
    )
}

export default GlobalToastContainer;

 

그리고 이 컴포넌트를 index.js에서 호출하여 root에 위치시키면 전역 컴포넌트로 만들 수 있습니다.

이제 토스트 메세지를 사용하는 곳마다 return 문에 Toaster 컴포넌트를 작성할 필요가 없어졌습니다.

 

2번째로 일관된 토스트 사용법을 만들고 전역 토스트 컴포넌트를 관리하기 위하여 커스텀 훅을 만들었습니다.

hooks 폴더에 useToast라는 이름의 파일을 만들고

안에는 다음과 같은 2가지 유형의 커스텀 훅을 만들었습니다.

/**
 * @param {function} toastContent - 토스트 JSX를 반환하는 함수
 * @param {string} id - 토스트 아이디
 * @param {string} position - 토스트 위치(default는 bottom-center)
 * @param {string} type - 토스트 유형(default는 error)
 * @returns {object} showToast, dismissToast 함수를 포함하는 객체
 */
export const useToast = (
    toastContent, id, position = 'bottom-center',
    type = 'error') => {
	//생략
};


/**
 * @param {Promise} response - 프로미스 객체
 * @param {function} successFunc - 성공 시 실행할 로직 함수
 * @param {function} errorFunc - 에러 시 실행할 로직 함수
 * @returns {object} showPromiseToast 함수를 반환
 */
export const usePromiseToast = () => {	
    //생략
    const showPromiseToast = (response, successFunc, errorFunc) => {
        //생략
    }
    return { showPromiseToast }
}

이제 토스트 메세지가 필요한 컴포넌트에 커스텀 훅을 호출하면 됩니다.

사용 방법은 아래와 같습니다.

//useToast
const {showToast: showMyDataErrorToast} = useToast(
	() => <span>
		프로필 정보를 가져오는데 실패했어요!
	</span>, 'my-data-error', 'bottom-center'
)

//usePromiseToast
const {showPromiseToast: showVerifyToast} = usePromiseToast();
showVerifyToast(response, 
	() => {
		//성공 시 코드
		return '인증되었습니다.'
			},
		() => {
		//실패 시 코드
		return '인증번호가 틀렸습니다.'
		}
)

 

3번 홈 화면에서 2개의 토스트 메세지를 띄우는 부분은 메세지를 하나로 합치기로 결정하였습니다.

어차피 사용자는 ‘해결하기’ 버튼을 한 번에 하나만 누를 수 있고

2가지를 태스크를 동시에 해결할 수 있는게 아니라면

굳이 2개의 토스트 메세지를 띄울 필요가 없다고 느꼈기 때문입니다.

 

따라서 알림 또는 위치 설정이 꺼져 있는 경우 ‘알림과 위치 설정이 꺼져있어요!’라는 단일 토스트 메세지를 표시합니다.

기존의 플로우를 약간 수정하여 토스트 메세지의 '해결하기'버튼을 클릭하면 마이페이지로 리다이렉트하도록 하였습니다.

이 때 해결이 필요한 메뉴 옆에 경고 아이콘을 넣어 사용자로 하여금 어떤 메뉴가 꺼져 있는 것인지 알 수 있도록 하였다.

 

직관적으로 알아차릴 수 있도록 아이콘에 모션도 넣어놨습니다.

해당 토스트 메세지는 로그인 시 한 번 띄우는 걸로 구현하였습니다.

수정 후 화면 플로우

실행 화면 영상

수정 후 실행화면

 

수정 전후 코드 구조

수정 전후 코드 구조

후기

토스트 메세지를 전역 컴포넌트로 관리하면서

기존에 반복되거나 분산 되어있던 토스트 관련 코드들을 한 곳에 모을 수 있게 되었습니다.

이를 통해 코드의 가독성이 높아졌고, 유지보수성도 향상될 수 있었습니다.

 

또한 토스트 메세지 커스텀 훅을 만들어서 필요한 곳에서 손쉽게 재사용할 수 있게 되어 개발의 생산성 또한 높아지게 되었습니다.

리팩토링을 통해 코드의 품질이 향상되는 것을 눈으로 보고 느끼면서 더 나은 리팩토링 방법에 대해 탐구하고 싶어졌습니다.

 

홈 화면에서 토스트 메세지가 새로고침 버튼을 가리는 문제를 해결하는 점에 있어서는

사용자 경험을 향상시키는 것이 매우 어렵다는 것을 느꼈습니다.

 

알림과 GPS 설정은 디스턴스에 있어 매우 중요한 기능이지만,

자율적으로 설정을 꺼두는 사용자의 편의성도 고려해야 하기 때문에

사용자가 알림을 원하지 않는 경우에도 방해하지 않는 선에서 적절한 알림을 제공해야 했습니다.

 

이 부분에 대해 디자이너와 회의를 하며 다양한 방법을 연구하였고

2개의 토스트 메세지를 합하고 버튼을 줄바꿈 하여 사용자의 선택권을 보장하면서

알림 기능도 놓치지 않는 효과적인 기능 구현을 할 수 있었습니다.

관련글 더보기