ํด๋น ํ์ด์ง ์ฌ์ฉ์ ๋ช ๊ฐ์ง ๊ท์น์ด ์๋ค.
๊ทธ ๊ท์น์ ์ด๊ฒผ์ ๋, ์ฌ์ฉ์์๊ฒ ์๋ฆฌ๊ณ ์ถ๋ค.
๋ฆฌ์กํธ์ ์ฌ์ฉํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ํ๋๋ฅผ ์ ํํด
์ฌ์ฉ์์๊ฒ ๊ท์น์ ์๋ ค๋ณด์!
https://lotto-magic-frontend.vercel.app/
๋ก๋ ๋ฒํธ ์์ฑ ๋ง๋ฒ์ง
ํ์ด ์์ 3๊ฐ๋ฅผ ์ ํํ๋ฉด ์ค๋์ ๋ก๋ ๋ฒํธ์ ํ์ด ์ ์๋ฅผ ๋ง๋ค์ด์ฃผ๋ ์ฌ์ดํธ์ ๋๋ค.
lotto-magic-frontend.vercel.app
1. ํ๋ฉด


2. ๋ค์ํ ์๋ฆผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- React-toastify
: ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. React ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์๊ฒ ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ๊ธฐ๋ฅ์ด ๋ง๊ณ ์์ ์ ์ด๋ค. ๊ธฐ๋ณธ ์คํ์ผ์ด ์์ด์ ์ปค์คํ ์ด ๋ง์ด ํ์ํ์ง ์์ ๋ ํธ๋ฆฌํ๋ค. - Sweetalert2
: ๊ธฐ์กด์ alert ์ฐฝ๋ณด๋ค ๋ค์ํ ๋์์ธ๊ณผ ์๊ฐ์ผ๋ก ๋ง๋ค์ด์ง alert ์ฐฝ์ด๋ค. ํ ์ข ๋ฅ๋ง ์์ง ์๊ณ alert, confirm, prompt ์ ๋ ฅ์ฐฝ ์ญ์ ์ง์ํ๋ฉฐ ์ฌ๋ฌ ์ฃผ์ ์ ๋ฐ๋ฅธ ๋ค์ํ ์๋์ฐฝ์ ๋ฐ๋ก css ์์ ์์ด ๊ณ ํ๋ฆฌํฐ์ ์ ๋๋ฉ์ด์ ์ฐฝ์ ๊ตฌํํ ์ ์๋ค. ์ฌ์ฉ์์๊ฒ ํ์ธ/์ทจ์๊ฐ ํ์ํ ๋ชจ๋ฌ์ ์ฌ์ฉํด์ผํ ๋ ํธ๋ฆฌํ๋ค. ๊ฐ๋ฒผ์ด ์๋ด๋ณด๋ค๋ ์กฐ๊ธ ๋ฌด๊ฑฐ์ด ๋๋์ด ์๋ค. - Sonner
: React์ ์ ์ธ์ ๊ตฌ์กฐ์ ์ต์ ํ๋ ํ ์คํธ์ด๋ค. ๊ฐ๋ฒผ์ฐ๋ฉฐ Framer Motion ์คํ์ผ๋ก ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์ ์ ๊ตฌํํ๋ค. Promise ์ฒ๋ฆฌ๋ก ๋ก์ง์ ์์ฑํ๊ธฐ ๋งค์ฐ ๊ฐํธํ๋ค. headless ๋ชจ๋๋ฅผ ์ง์ํ์ฌ ๊ฐ๋ฐ์๊ฐ ์ํ๋ ๋๋ก ๋์์ธํ๊ธฐ๊ฐ ๋งค์ฐ ์ฝ๋ค. Tailwind CSS์ ๊ถํฉ์ด ์ข์ ํธ์ด๋ค. - ๊ทธ ์ค Sonner๋ฅผ ์ ํํ๋ค. ์ผ๋จ ์ ์ผ ํฐ ์ด์ ๋ ๋ด๊ฐ ์ํ๋ ๋ชจ์ต ๊ทธ๋๋ก CSS๋ฅผ ๊ตฌํํ ์ ์๋ค๋ ์ ์ด ๊ฐ์ฅ ์ปธ๋ค. ์กฐ์ดํ๊ธด ํด๋ ๋๋ฆ '๋ง๋ฒ'์ ๊ด๋ จํ ํ์ด์ง๋ผ์(?) ์ผ์ ํ ํ ๋ง๋ฅผ ์ํ๋ค. ๋ ์ด ํ๋ก์ ํธ์ ๋ก์ง์ ์ฌ์ฉ์์ ์ํธ์์ฉ ์์ด ๋ฒํธ๋ง ๋ณด์ฌ์ฃผ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ์์ฃผ ๊ฐ๋ณ๊ฒ ๋์ํ ์ ์๋ Sonner๊ฐ ์๋ง๋ค๊ณ ๋๊ผ๋ค. ๊ผญ Sonner๋ฅผ ์ฌ์ฉํ ํ์ ์์ด, ๋ณธ์ธ ํ๋ก์ ํธ์ ๋ง๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค. ๊ด๋ จ ๋งํฌ๋ ์๋์ ์ฒจ๋ถํ๊ฒ ๋ค.
React์์ react-toastify๋ก ํจ๊ณผ์ ์ธ ์๋ฆผ ๊ตฌํํ๊ธฐ
React์์ react-toastify๋ก ํจ๊ณผ์ ์ธ ์๋ฆผ ๊ตฌํํ๊ธฐ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ์ฌ์ฉ์์๊ฒ ์์ ์ ์ฑ๊ณต, ์คํจ, ์งํ ์ํ ๋ฑ์ ์๋ ค์ค ํ์๊ฐ ์์ต๋๋ค. ์ด๋ฐ ์๋ฆผ(notification)์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ
hell-of-company-builder.tistory.com
๐จ SweetAlert2 - ์ด์ alert ๋ชจ๋ฌ์ฐฝ ์ค์น & ์ฌ์ฉ๋ฒ
SweetAlert2 ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์น ํ๋ก๊ทธ๋๋ฐ์ ํ๋ค ๋ณด๋ฉด ์์ฃผ Alert ํจ์๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. Alert๋ ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ ์ฃผ๊ณ ์ ํ ๋ ์ ๋ง ์๊ตฌ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ ์ ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์๋ฐ์คํฌ๋ฆฝ
inpa.tistory.com
- React์ ์ ์ฉํ๊ธฐ ์ข์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชจ์
[๋ฒ์ญ] ํ๋ก์ ํธ๋ฅผ ๊ฐํํ ์ ์๋ 17๊ฐ React ๋ผ์ด๋ธ๋ฌ๋ฆฌ
Let's Code Future ๋์ 25.01.20์ผ์ ํฌ์คํธ ์ ๋ฒ์ญ๊ธ์ ๋๋ค https://medium.com/@letscodefuture/top-16-modern-react-libraries-to-supercharge-your-next-big-project-78e912e95014 Top 16+ Modern React Libraries To Supercharge Your Next Big Proje
daunje0.tistory.com
3. Sonner
- Sonner ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
npm install sonner

- ํ ์คํธ ๋ง๋ค ํ์ผ ๋ง๋ค๊ธฐ
๋ด๊ฒฝ๋ก: components/common/MagicToast.tsx

- ํ ์คํธ ์ฝ๋ ์์ฑํ๊ธฐ
1) ์ ์ฒด ์ฝ๋
'use client';
import { Toaster, toast } from 'sonner';
type MagicToastOptions = {
duration?: number;
};
export function showMagicToast(
message: string,
options?: MagicToastOptions
) {
toast.custom(
(toastId) => (
<button
type="button"
onClick={() => toast.dismiss(toastId)}
style={{
width: 'min(286px, calc(100vw - 48px))',
minHeight: '52px',
padding: '14px 22px',
transform: 'translateX(12.5%)',
border: 'none',
borderRadius: '28px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
cursor: 'pointer',
background:
'linear-gradient(90deg, rgba(181, 162, 242, 0.96) 0%, rgba(193, 232, 252, 0.96) 100%)',
color: '#111111',
fontSize: 'clamp(0.88rem, 3.6vw, 1.08rem)',
fontWeight: 700,
lineHeight: 1.25,
fontFamily: 'OngleipParkDahyeon, sans-serif !important',
boxShadow:
'0 16px 38px rgba(134, 104, 214, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.55)',
backdropFilter: 'blur(6px)',
}}
>
{message}
</button>
),
{
duration: options?.duration ?? 1700,
position: 'top-center',
}
);
}
export function MagicToaster() {
return (
<Toaster
position="top-center"
duration={1900}
visibleToasts={2}
gap={14}
offset={70}
/>
);
}
2) ์ค๋ช ํฌํจ๋ ์ฝ๋
'use client';
import { Toaster, toast } from 'sonner';
// showMagicToast ํจ์์ ์ถ๊ฐ๋ก ๋ฃ์ ์ ์๋ ์ต์
์ ๋ชจ์์ ์ ํด๋๋ค.
type MagicToastOptions = {
// ํ์ฌ ์ต์
์ ํ๋๋ง ์ค์ ํ๋ค.
// ?์ ๋ถ์ฌ์ duration์ ์ ํ๊ฐ์ผ๋ก ์์ด๋ ๋๊ณ ์์ด๋ ๋๋ค๋ ์๋ฏธ์ด๋ค.
// showMagicToast('์์๋ฅผ 3๊ฐ ์ด์ ์ ํํด์ฃผ์ธ์.', {duration: 3000,});
// ๋ง์ฝ ํน์ ํ ์คํธ๋ง 3000์ผ๋ก ์ค์ ํ๋ฉด 3์ด๊ฐ ๋ณด์ด๋๋ก ํ๋ ๊ฒ์ด๋ค.
// ์ค์ ํ์ง ์์ผ๋ฉด ์๋ ์ค์ ํ ๊ฐ์ผ๋ก ๋ณด์ธ๋ค.(1.7์ด)
duration?: number;
};
// ๋ค๋ฅธ ์ปดํฌ๋ํ์์ ์ฌ์ฉํ ์ ์๋๋ก export ํ๋ค.
export function showMagicToast(
// showMagicToast ํจ์๊ฐ ๋ฐ์ ๊ฐ๋ค์ ์ ํ๋ ๋ถ๋ถ์ด๋ค.
// showMagicToast(1_message, 2_MagicToastOption)์ด ๋๋ค.
message: string,
options?: MagicToastOptions
) {
toast.custom(
(toastId) => (
<button
type="button"
onClick={() => toast.dismiss(toastId)}
style={{
width: 'min(286px, calc(100vw - 48px))',
minHeight: '52px',
padding: '14px 22px',
transform: 'translateX(12.5%)',
border: 'none',
borderRadius: '28px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
cursor: 'pointer',
background:
'linear-gradient(90deg, rgba(181, 162, 242, 0.96) 0%, rgba(193, 232, 252, 0.96) 100%)',
color: '#111111',
fontSize: 'clamp(0.88rem, 3.6vw, 1.08rem)',
fontWeight: 700,
lineHeight: 1.25,
fontFamily: 'OngleipParkDahyeon, sans-serif !important',
boxShadow:
'0 16px 38px rgba(134, 104, 214, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.55)',
backdropFilter: 'blur(6px)',
}}
>
{/* ์ ๋ฌ๋ฐ์ message๋ฅผ ํ ์คํธ ์์ ํ์ํ๋ค. */}
{message}
</button>
),
{
// ํ ์คํธ๊ฐ ์๋์ผ๋ก ์ฌ๋ผ์ง๊ธฐ๊น์ง์ ์๊ฐ์ด๋ค.(1.7์ด)
// options ์์ duration์ด ์์ผ๋ฉด ๊ทธ ๊ฐ์ ์ฌ์ฉํ๊ณ
// ์์ผ๋ฉด 1700ms๋ฅผ ์ฌ์ฉํ๋ค.
duration: options?.duration ?? 1700,
position: 'top-center',
}
);
}
// MagicToaster๋ ์ค์ ํ ์คํธ๊ฐ ๋ ๋๋ง ๋ ๊ณต๊ฐ์ ๋ง๋๋ ์ปดํฌ๋ํธ์ด๋ค.
// Next.js์ ๊ฒฝ์ฐ layout.tsx์ ๋ฃ์ด ์ฑ ์ ์ฒด์์ ํ ๋ฒ๋ง ๋ ๋๋งํ๋ฉด ๋๋ค.
export function MagicToaster() {
return (
<Toaster
position="top-center"
duration={1700}
// ๋์์ ๋ณด์ผ ์ ์๋ ํ ์คํธ ํ๋ฉด ๊ฐ์
// ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ์ฐ์์ผ๋ก ํด๋ฆญํด๋
// ํ ์คํธ๊ฐ 2๊ฐ ์ด์์ผ๋ก ๋ณด์ด์ง ์๊ฒ ๋ง์๋ค.
visibleToasts={2}
// ํ ์คํธ๊ฐ ์ฌ๋ฌ๊ฐ ๋ณด์ผ ๋ ๋ ์ฌ์ด์ ๊ฐ๊ฒฉ์ด๋ค.
gap={14}
// ํ๋ฉด์์ ์ผ๋ง๋ ๋จ์ด๋จ๋ฆด์ง ์ค์ ํ๋ค.
// top-center๋ฅผ ๋ฏธ๋ฆฌ ์ค์ ํ์ผ๋ ๋งจ ์์์ 70px ์๋๋ก ํ์๋๋ค.
offset={70}
/>
);
}
4. showMagicToast ์ฝ๋์ ์ ์ฉํ๊ธฐ
- ์์๋ฅผ 3๊ฐ ์ด์ ์ ํํ์ ๋
if (selectedOptions.length >= 3) {
showMagicToast('์์๋ 3๊ฐ๊น์ง๋ง ์ ํํ ์ ์์ด์.');
return;
}
MagicToast.tsx์์ message: string, options?: MagicToastOptions ์ด ์ฝ๋๋ฅผ ์ ์ํ๋ค. ์ค๋ช ์ผ๋ก showMagicToast(1_message, 2_MagicToastOption)๊ฐ ๋๊ณ ์ฌ๊ธฐ์ ๋๋ฒ์งธ์ duration: 3000๊ณผ ๊ฐ์ ์ ๋ ฅ์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ 1700์ด ๋๋ค๊ณ ํ๋ค.
๋ค์ ์ฝ๋๋ฅผ ์ดํด๋ณด์. ๋ฉ์์ง๋ง ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ํด๋น ๋ฉ์์ง๋ ๊ธฐ๋ณธ๊ฐ์ธ 1.7์ด ๋์ ๋ณด์ด๊ณ ์ฌ๋ผ์ง๋ค.
if (selectedOptions.length >= 3) {
showMagicToast('์์๋ฅผ 3๊ฐ ์ด์ ์ ํํด์ฃผ์ธ์.', {duration: 3000,});
return;
}
์ฌ๊ธฐ์๋ ๋ฉ์์ง์ duration์ ํจ๊ป ๋ณด๋๋ค. ๊ทธ๋ฌ๋ฉด ํด๋น ๋ฉ์์ง๋ ๋ฐ๋ก ์ค์ ํ 3์ด ๋์ ๋ณด์ด๊ณ ์ฌ๋ผ์ง ๊ฒ์ด๋ค. ์ด๋ฐ์์ผ๋ก ํ ์คํธ์ ๋ฉ์์ง์ ๋ณด์ฌ์ค ์๊ฐ์ ์ ๋ ฅํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋ค.
4. layout.tsx
// MagicToaster๋ ์ค์ ํ ์คํธ๊ฐ ๋ ๋๋ง ๋ ๊ณต๊ฐ์ ๋ง๋๋ ์ปดํฌ๋ํธ์ด๋ค.
// Next.js์ ๊ฒฝ์ฐ layout.tsx์ ๋ฃ์ด ์ฑ ์ ์ฒด์์ ํ ๋ฒ๋ง ๋ ๋๋งํ๋ฉด ๋๋ค.
export function MagicToaster() {
return (
...
/>
);
}
return (
<html lang="ko">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
<MswProvider>
<QueryProvider>
{children}
</QueryProvider>
<MagicToaster />
</MswProvider>
</body>
</html>
);
}
MagicToast.tsx์ ๋ง์ง๋ง ๋ถ๋ถ์ ๋ณด๋ฉด MagicToaster ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๋ค. MagicToaster๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ง์ ๋์ฐ๋ ์ฝ๋๊ฐ ์๋๋ผ, ํ ์คํธ๊ฐ ํ๋ฉด์ ๋ํ๋ ์ ์๋๋ก ์ค๋นํด์ฃผ๋ ์ถ๋ ฅ ๊ณต๊ฐ์ด๋ค. ๊ทธ๋์ ์ฑ ์ ์ฒด์ ํ ์คํธ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก layout.tsx์ children ์๋์<MagicToaster/>๋ฅผ ๋ฃ์๋ค.
๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ MswProvider์ ๊ฐ์ด {children}์ ๊ฐ์ผ ํํ๊ฐ ์๋ ์ด์ ๋, MagicToaster๊ฐ children์๊ฒ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ provider ์ญํ ์ ํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. ํ ์คํธ๋ ๋จ์ํ ์๋ฆผ์ด ๋ฐ ์ ์๋ ๊ณต๊ฐ๋ง ์ค์นํ๋ฉด ๋๋ค. ๊ทธ๋์ ๊ตณ์ด children์ ๊ฐ์ ํ์๊ฐ ์๋ค.
ํ ์คํธ๋ ํน์ ํ์ด์ง์ ์ผ๋ถ๋ผ๊ธฐ๋ณด๋ค๋ ์ฑ ์ ์ฒด์ ๋ ๋ค๋๋ ์ ์ญ ์๋ฆผ์ด๋ค. ์ด ์๋ฆผ์ ํ์ด์ง ์์ชฝ ๋ฐ์ค์ ๋ค์ด๊ฐ๋ ๊ฒ ์๋๋ผ ํ๋ฉด ์๋จ์ ๋ฅ์ค ๋ ์ผํ๋ค. ๊ทธ๋์ ํน์ ํ์ด์ง ์ปดํฌ๋ํธ ์์ ๋ฃ๊ธฐ๋ณด๋ค layout.tsx์ ์ ์ญ์ผ๋ก ํ ๋ฒ ๋ฃ๋๊ฒ ์ข๋ค.