개발하다 보면 사용자에게 작업의 성공, 실패 등을 알려주는 토스트 메세지 (mui 에서는 snackbar)가 필요하다.처음에는 단순하게 alert 으로 알려주었다가 그래도 프론트엔드 개발자를 꿈꾸는데 언제까지 alert 으로 알려주기에는 가오(?)가 안살기에 한번 달아보았다. 처음에는 단순하게 각 컴포넌트에서 토스트 메세지 상태를 관리했다. 아래 코드처럼 말이다.
// 토스트 관리를 위한 상태
const [snackMessage, setSnackMessage] = useState("");
const [snackColor, setSnackColor] = useState("success");
const [state, setState] = useState({
open: false,
Transition: SlideTransition,
});
// 토스트 핸들러
const showSnack = (message, type = "success") => {
setSnackMessage(message);
setSnackColor(type);
setState({ open: true, Transition: SlideTransition });
};
const handleSnackClose = () => {
setState({ ...state, open: false });
};
return (
{/* 위에서 컴포넌트를 만든 후 */}
<ToastContext.Provider value={{ showSnack }}>
{children}
<Snackbar
open={state.open}
autoHideDuration={3000}
onClose={handleSnackClose}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
slots={{ transition: state.Transition }}
>
<Alert onClose={handleSnackClose} severity={snackColor}>
{snackMessage}
</Alert>
</Snackbar>
</ToastContext.Provider>
);
이렇게 해서 토스트를 만들어주었다. 그리고 여기저기 컴포넌트에 토스트가 필요한 곳에 넣어주었다. 그러다가 문득 생각이 들었다. 왜 여기저기에 복붙해서 넣어줘야하나? 이것도 훅 비스무리로 만들 수 없을까 라는 생각말이다.
여기서 문제점이 들어난 것이다.
이 방식의 문제점은 토스트 메세지가 필요한 모든 컴포넌트에서 동일한 코드를 반복해야 한다는 것이다. 같은 코드를 여러 곳에 복붙하는것은 비효율적이며 유지보수를 어렵게 만든다. 어떻게 하면 효율적으로 관리할 수 있을까?
이런 상황에서 React의 Context API가 해결책이 될 수 있다. React Context는 컴포넌트 트리 전체에 데이터를 제공하는 방법이다. 마치 지금 내가 쓰고 있는 Jotai 느낌이지만 조금 다르긴 하다. 이에 대해서는 후술할 예정이다.
일반적으로 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 데이터를 전달하는데, 여러 단계의 컴포넌트를 거쳐야 하는 경우 번거로워진다. Context를 사용하면 트리 구조내 어느 컴포넌트에서든 필요한 데이터에 직접 접근할 수 있다.
Context API의 주요 구성 요소는 다음과 같다:
createContext: Context 객체를 생성한다.
Provider: Context의 값을 하위 컴포넌트에 제공한다.
useContext: Context의 값을 읽는 Hook 이다.
먼저 토스트 메세지 관리를 위한 Context와 Provider를 만들었다.
"use client";
import { Alert, Slide, Snackbar } from "@mui/material";
import { createContext, useContext, useState } from "react";
const ToastContext = createContext(null);
function SlideTransition(props) {
return <Slide {...props} direction="left" />;
}
export function ToastProvider({ children }) {
// 토스트 관리를 위한 상태
const [snackMessage, setSnackMessage] = useState("");
const [snackColor, setSnackColor] = useState("success");
const [state, setState] = useState({
open: false,
Transition: SlideTransition,
});
// 토스트 핸들러
const showSnack = (message, type = "success") => {
setSnackMessage(message);
setSnackColor(type);
setState({ open: true, Transition: SlideTransition });
};
const handleSnackClose = () => {
setState({ ...state, open: false });
};
return (
<ToastContext.Provider value={{ showSnack }}>
{children}
<Snackbar
open={state.open}
autoHideDuration={3000}
onClose={handleSnackClose}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
slots={{ transition: state.Transition }}
>
<Alert onClose={handleSnackClose} severity={snackColor}>
{snackMessage}
</Alert>
</Snackbar>
</ToastContext.Provider>
);
}
export const useToast = () => useContext(ToastContext);
createContext 함수를 사용하여 Toastcontext 라는 새로운 Context를 생성했다.
ToastProvider 컴포넌트는 토스트 메세지 상태를 관리하고, showSnack 함수를 Context 값으로 제공한다. 또한 실제 Snackbar 컴포넌트를 렌더링한다.
useToast 라는 커스텀 훅을 만들어서 어디서든 쉽게 Context 값을 가져올 수 있게 하였다.
<body className={`${notoSans.variable} antialiased max-w-[1920px] w-full`}>
<ClientProvider>
<ToastProvider>{children}</ToastProvider>
</ClientProvider>
</body>
다음은 layout 에 Provider 를 적용시켜줬다.
다음은 토스트 메세지를 사용한 사례이다.
import { useToast } from "/ToastContext"
export default function Page() {
const { showSnack } = useToast();
const handleCopy = async () => {
// copy 로직 실행
try {
...
} catch (error) {
console.error("Failed to copy: ", error)
showSnack("클립보드 복사에 실패했습니다.", "error")
} finally {
showSnack("복사 완료", "success");
}
};
return (
{/* 컴포넌트 */}
<button onClick={handleCopy}> 복사하기 </button>
);
}
이렇게 하면 각 컴포넌트에서 반복적으로 토스트 관련 코드를 작성할 필요가 없다. useToast 훅을 사용하면 어디서든 쉽게 토스트 메세지를 표시할 수 있다.
기존 코드와 비교했을 때 useToast() 한번이면 끝나니깐 굉장히 편리하고 보기에도 좋아졌다.

Context API 가 적합한 경우:
간단한 전역 상태를 관리할 때
추가 라이브러리 의존성을 피하고 싶을 때
UI 테마, 사용자 인증 등 자주 변경되지 않는 상태
토스트 메세지처럼 중앙에서 관리되는 UI 상태
Jotai 가 적합한 경우:
복잡한 상태 로직이 필요할 때
성능 최적화가 중요할 때
여러 작은 상태들이 독립적으로 변경될 때
상태 간 파생 관계가 많을 때
내가 쓴 토스트 메세지 같은 간단한 UI 상태 관리는 Context API 로도 충분하다. 하지만 애플리케이션이 성장하고 상태 관리가 복잡해진다면 Jotai 같은 라이브러리를 고려해볼 수 있다.
React Context 와 Provider 패턴을 사용하면 토스트 메세지와 같은 글로벌 상태를 효율적으로 관리할 수 있다. 이 패턴은 다음과 같은 이점을 있다:
코드 중복을 줄일 수 있다.
컴포넌트 간 props 전달을 최소화할 수 있다.
관심사를 분리하여 코드 구조를 개선할 수 있다.
유지보수가 더 쉬워진다.
이 패턴은 토스트 메시지뿐만 아니라 테마, 사용자 인증, 언어 설정 등 애플리케이션 전체에서 공유해야 하는 상태를 관리하는 데도 유용하게 활용할 수 있다.
더 복잡한 상태 관리가 필요할 경우 Jotai, Recoil, Zustand 같은 라이브러리를 검토해볼 수 있지만, 간단한 사용 사례에는 React의 기본 기능만으로도 충분하다.
React Context API나 적절한 상태 관리 라이브러리를 활용하면 더 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있다. 프로젝트의 복잡성과 요구사항에 맞게 적절한 도구를 선택하는 것이 중요하다.