Recoil으로 전역으로 상태를 관리하며 위처럼 토스트 메시지 띄우는 작업을 커스텀 훅을 만들어 구현해보고자 한다.
타임아웃을 기본값으로 3초를 설정하여, 생성 3초 후 자동으로 없어지도록 하고, 이 타임아웃 값은 메시지 별로 설정 가능하다.
Recoil + 커스텀 훅으로 토스트 메시지를 구현한 이유는
- 토스트 컴포넌트를 사용할 때마다 직접 추가하지 않아도 된다. (App 파일 에서만 추가 )
- 모든 페이지에서 사용가능성이 있다. (중복 최소화)
- 페이지가 이동되어도 메시지들이 유지되어야 한다. (Recoil 로 전역관리 )
구현할 것들은
- 토스트 메시지 타입 선언 (ToastMessage.ts)
- 토스트 메시지 전역 상태 관리 변수 (ToastMessageState.ts)
- 토스트 메시지 아이템 컴포넌트 (ToastMessageItem.tsx)
- 토스트 메시지 컨테이너 컴포넌트 (항상 상위에 유지, ToastMessageContainer.tsx)
- 토스트 메시지를 추가하고, 삭제하는 함수를 포함하고 있는 커스텀 훅 (useToast.tsx)
토스트 메시지 타입
▼ ToastMessage.ts
export default interface ToastMessage {
id?: number;
content: string;
type?: "default" | "success" | "error";
timeout?: number;
}
- id
- 3초 후 메시지를 삭제할 때 해당 메시지를 판별하기 위한 고유값으로서, 생성 시에 자동으로 Date.now()값(타임스탬프)을 대입하여 고유하도록 지정한다.
- content
- 메시지에 포함할 내용을 의미한다.
- type
- default , success, error 중 하나로, 각 type에 대한 스타일을 구분하기 위한 속성이다.
- timeout
- 메시지가 사라질 시간(ms)를 의미한다.
토스트 메시지 전역 상태 관리 변수 (Recoil)
먼저, recoil이 설치되어있지 않다면, 설치해야 한다.
$ yarn add recoil
루트 파일인 main.tsx에서 App을 RecoilRoot로 감싸준다.
▼ main.tsx
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<RecoilRoot>
<App /> /* RecoilRoot로 App 컴포넌트 감싸주기 */
</RecoilRoot>
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
토스트 메시지 state를 저장하는 atom 을 정의한다.
▼ src/recoil/atom/ToastMessageState.ts
import { atom } from "recoil";
import ToastMessage from "../../types/ToastMessage";
export const toastMessageState = atom<ToastMessage[]>({
key: "toastMessagesState",
default: [],
});
해당 atom을 조회하는 key값을 toastMessageState로 지정하고,
기본 값으로는 메시지가 하나도 없을 경우인 빈 배열을 지정한다.
이제 아래처럼, 키 값으로 지정한 toastMessageState으로 전역 변수의 상태값을 사용하고, 업데이트할 수 있다.
const toasts = useRecoilValue(toastMessageState); // 상태
const setToasts = useSetRecoilState(toastMessageState); // 상태 업데이트
토스트 메시지 아이템 컴포넌트
▼ src/components/Toast/ToastMessageItem.tsx
import ToastMessage from "../../../types/ToastMessage";
const ToastMessageItem = ({ type, content }: ToastMessage) => {
return (
<div
className={`my-1 w-[20vw] rounded-md flex items-center text-center px-4 py-2 justify-center text-sm ${
type == "success"
? "bg-primary-green text-white "
: type == "error"
? "bg-error-red text-white "
: "bg-black text-white"
}`}
>
{content}
</div>
);
};
export default ToastMessageItem;
type에 따라 원하는 대로 스타일을 다르게 지정해주고,
content 내용을 띄운다 .
토스트 메시지 컨테이너 컴포넌트
▼ src/component/Toast/ToastMessageContainer.tsx
import { useRecoilValue } from "recoil";
import { toastMessageState } from "../../../recoil/atom/ToastMessageState";
import ToastMessageItem from "./ToastMessageItem";
const ToastMessageContainer = () => {
const toasts = useRecoilValue(toastMessageState); // 전역 토스트 상태 가져오기
return (
<div className="fixed top-0 left-1/2 -translate-x-1/2 z-10">
{toasts &&
toasts.map(({ content, type, id }) => (
<ToastMessageItem content={content} type={type} key={"toast" + id} />
))}
</div>
);
};
export default ToastMessageContainer;
여기서 Recoil을 이용해 저장한 메시지의 전역 상태값을 사용한다.
const toasts = useRecoilValue(toastMessageState);
해당 toasts가 빈 배열이 아니면, map으로 ToastMessageItem 컴포넌트를 호출한다.
가장 상단 가운데에 고정되도록 스타일을 지정해준다. (fixed top-0 left-1/2 -translate-x-1/2
)
그리고 이 토스트 컨테이너를 모든 페이지에 적용하기 위해 App에서 호출한다. (다른 코드는 생략 )
▼ App.tsx
/* ... */
import ToastMessageContainer from "./components/common/Toast/ToastMessageContainer";
function App() {
return (
/* ... */
<ToastMessageContainer />
/* ... */
);
}
export default App;
토스트 커스텀 훅
▼ src/hooks/useToast.tsx
import { useSetRecoilState } from "recoil";
import { toastMessageState } from "../recoil/atom/ToastMessageState";
import ToastMessage from "../types/ToastMessage";
export function useToast() {
const setToasts = useSetRecoilState(toastMessageState);
const addToast = ({
content,
type = "default",
timeout = 3000,
}: ToastMessage) => {
const newToast = { id: Date.now(), content, type };
setToasts((prev) => [...prev, newToast]);
setTimeout(() => {
removeToast(newToast.id);
}, timeout);
};
const removeToast = (id: number) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
return { addToast, removeToast };
}
setToast
토스트 상태를 업데이트하는 setToasts 를 가져온다.
const setToasts = useSetRecoilState(toastMessageState);
addToast
- 토스트 메시지를 추가하는 함수로서, 필요한 곳에서 이 함수를 호출함으로써 메시지를 띄울 수 있다.
- type은 “default”가 기본값이 되도록, timeout은 3000(3초)가 기본 값이 되도록 지정한다.
type = "default", timeout = 3000
newToast
- id 값은 Date.now() ( 타임스탬프) 값을 지정해주어 고유한 값이 되도록 하고, 메시지의 내용이 되는 넘겨받은 content와 메시지의 스타일을 결정하는 type을 지정해준다.
const newToast = { id: Date.now(), content, type };
setToasts((prev) => [...prev, newToast]);
: newToast를 toast state에 추가하도록 setToasts를 호출하고, newToast를 추가한 배열을 넘겨주어 상태를 업데이트한다.
setTimeout(() => {
removeToast(newToast.id);
}, timeout);
: timeout값이 되면 해당 메시지가 삭제되도록 하기 위해 timeout이 종료되면 removeToast를 호출하고, 해당 토스트의 아이디를 넘겨준다.
removeToast
- 넘겨받은 id 값에 해당하는 메시지를 삭제한 (filter) 메시지 배열로 state를 업데이트한다.
토스트 메시지 추가 함수 호출
이제 토스트 메시지를 생성하는 함수 addToast로 토스트를 생성할 수 있다.
임의로, 어떤 버튼을 눌렀을 때, "Toast Message! " + new Date().toLocaleTimeString() 을 content로 하도록 addToast를 호출해본다.
addToast({
content: "Toast Message! " + new Date().toLocaleTimeString(),
type: "success",
});
아래처럼, 버튼을 클릭할 때마다 메시지가 생기고, 3초 후에 해당 메시지가 사라지는 것을 확인할 수 있다!
'FRONTEND > React' 카테고리의 다른 글
[ React ] 라이브러리 없이 모달(컨펌창) 구현하기 (0) | 2024.03.30 |
---|---|
[ React ] 15초 후 다음 페이지로 자동 이동 기능 (0) | 2024.03.24 |
[ React + vercel ] vercel로 배포한 React 앱에 vercel analytics 사용하기 (0) | 2024.03.19 |
[ React ] 더 나은 사용자 경험을 위한 스켈레톤 UI (1) | 2024.03.14 |
[ React ] textarea / input 태그 maxLength 속성 한글에서만 작동하지 않음 해결 (0) | 2024.03.08 |