Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit c64d172

Browse files
onemeeeyeolyigoranikinlchanss
authored
📦️ useContext -> zustand 마이그레이션 (#388)
Co-authored-by: 이성열 <yeolyi1310@gmail.com> Co-authored-by: goranikin <goranikin@snu.ac.kr> Co-authored-by: Limchansol <lchans0319@gmail.com>
1 parent 900234a commit c64d172

File tree

23 files changed

+1826
-5023
lines changed

23 files changed

+1826
-5023
lines changed

actions/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Role } from '@/apis/types/role';
66
import { getMockLogin } from '@/apis/v2/mock-login';
77
import { getMyRole } from '@/apis/v2/user/my-role';
88
import { COOKIE_SESSION_ID } from '@/constants/network';
9-
import { UserState } from '@/contexts/SessionContext';
9+
import { UserState } from '@/stores/SessionStore';
1010

1111
export const setMockAuthCookie = async (role: Role) => {
1212
const resp = await getMockLogin(role);

app/.internal/layout.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import { getMessages } from 'next-intl/server';
88
import { ToastContainer } from 'react-toastify';
99

1010
import ModalContainer from '@/components/modal/ModalContainer';
11-
import ModalContextProvider from '@/contexts/ModalContext';
12-
import SessionContextProvider from '@/contexts/SessionContext';
1311

1412
export default async function RootLayout(props: {
1513
children: React.ReactNode;
@@ -25,13 +23,9 @@ export default async function RootLayout(props: {
2523
<html lang={params.locale} className="font-normal sm:min-w-[1000px]">
2624
<body>
2725
<NextIntlClientProvider messages={messages}>
28-
<SessionContextProvider>
29-
<ModalContextProvider>
3026
{children}
3127
<ModalContainer />
3228
<ToastContainer />
33-
</ModalContextProvider>
34-
</SessionContextProvider>
3529
</NextIntlClientProvider>
3630
</body>
3731
</html>

app/[locale]/admin/helper/slide/SlideListHeader.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SLIDE_ROW_CELL_WIDTH } from '@/app/[locale]/admin/helper/slide/SlideListRow';
22

33
export default function SlideListHeader() {
4+
45
return (
56
<h5 className="flex h-10 items-center border-b border-t border-neutral-200 pr-[4.375rem] text-md tracking-wide text-neutral-700">
67
{/* 아래 본문 리스트와 간격 맞추기 위해 빈 탭 하나 필요 */}

app/[locale]/layout.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ import MobileNav from '@/components/layout/navbar/MobileNav';
1717
import Navbar from '@/components/layout/navbar/Navbar';
1818
import ModalContainer from '@/components/modal/ModalContainer';
1919
import { isBeta, isProd } from '@/constants/env';
20-
import ModalContextProvider from '@/contexts/ModalContext';
21-
import { NavbarContextProvider } from '@/contexts/NavbarContext';
22-
import SessionContextProvider from '@/contexts/SessionContext';
2320
import { Link, routing } from '@/i18n/routing';
2421

2522
const PROD_URL = 'https://cse.snu.ac.kr';
@@ -86,11 +83,7 @@ async function ContextProviders({ locale, children }: { locale: string; children
8683

8784
return (
8885
<NextIntlClientProvider messages={messages}>
89-
<SessionContextProvider>
90-
<ModalContextProvider>
91-
<NavbarContextProvider>{children}</NavbarContextProvider>
92-
</ModalContextProvider>
93-
</SessionContextProvider>
86+
{children}
9487
</NextIntlClientProvider>
9588
);
9689
}

components/common/LoginVisible.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { ReactNode } from 'react';
44

55
import { Role } from '@/apis/types/role';
6-
import { useSessionContext } from '@/contexts/SessionContext';
6+
import { useSessionStore } from '@/stores/SessionStore';
77

88
type Props = {
99
fallback?: ReactNode;
@@ -15,7 +15,7 @@ type Props = {
1515
};
1616

1717
export default function LoginVisible({ staff, children, fallback, role }: Props) {
18-
const { state } = useSessionContext();
18+
const state = useSessionStore((s) => s.state);
1919

2020
if (state === 'logout') return fallback;
2121
if (staff && state !== 'ROLE_STAFF') return fallback;

components/layout/header/HeaderRight.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { useTranslations } from 'next-intl';
55
import LoginVisible from '@/components/common/LoginVisible';
66
import HeaderSearchBar from '@/components/layout/header/HeaderSearchBar';
77
import { isProd } from '@/constants/env';
8-
import { useSessionContext } from '@/contexts/SessionContext';
98
import { Link } from '@/i18n/routing';
9+
import { useSessionStore } from '@/stores/SessionStore';
1010
import useLanguage from '@/utils/hooks/useLanguage';
1111

1212
export default function HeaderRight() {
@@ -41,7 +41,9 @@ const Divider = () => {
4141
};
4242

4343
const ProdLogin = () => {
44-
const { state, login, logout } = useSessionContext();
44+
const state = useSessionStore((s) => s.state);
45+
const login = useSessionStore((s) => s.login);
46+
const logout = useSessionStore((s) => s.logout);
4547
const t = useTranslations('Header');
4648

4749
const authText = t(state === 'logout' ? '로그인' : '로그아웃');
@@ -55,7 +57,9 @@ const ProdLogin = () => {
5557
};
5658

5759
const DevLogin = () => {
58-
const { state, mockLogin, mockLogout } = useSessionContext();
60+
const state = useSessionStore((s) => s.state);
61+
const mockLogin = useSessionStore((s) => s.mockLogin);
62+
const mockLogout = useSessionStore((s) => s.mockLogout);
5963

6064
const isLogout = state === 'logout';
6165

components/layout/header/MobileNavButton.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client';
22

33
import { about, main as mainNode, SegmentNode } from '@/constants/segmentNode';
4-
import { useNavbarContext } from '@/contexts/NavbarContext';
54
import MenuSVG from '@/public/image/header/menu.svg';
5+
import { useNavbarStore } from '@/stores/NavbarStore';
66
import useCurrentSegmentNode from '@/utils/hooks/useCurrentSegmentNode';
77

88
export default function MobileNavButton() {
@@ -20,7 +20,8 @@ export default function MobileNavButton() {
2020
}
2121

2222
const useMobileNav = () => {
23-
const { navbarState, setNavbarState } = useNavbarContext();
23+
const navbarState = useNavbarStore((s) => s.navbarState);
24+
const setNavbarState = useNavbarStore((s) => s.setNavbarState);
2425
const node = useCurrentSegmentNode();
2526

2627
const toggle = () => {
@@ -31,18 +32,18 @@ const useMobileNav = () => {
3132
// TODO: DOM을 직접 건들이지 않고 스크롤 방지 구현
3233
const main = document.querySelector('main') as HTMLElement;
3334

34-
setNavbarState((state) => {
35-
if (state.type === 'closed') {
36-
main.scrollTo(0, 0);
37-
main.style.overflow = 'hidden';
38-
main.style.height = '100%';
39-
return { type: 'hovered', segmentNode: nodeToOpen || about };
40-
} else {
41-
main.style.overflow = '';
42-
main.style.height = '';
43-
return { type: 'closed' };
44-
}
45-
});
35+
if (navbarState.type === 'closed') {
36+
main.scrollTo(0, 0);
37+
main.style.overflow = 'hidden';
38+
main.style.height = '100%';
39+
40+
setNavbarState({ type: 'hovered', segmentNode: nodeToOpen || about });
41+
} else {
42+
main.style.overflow = '';
43+
main.style.height = '';
44+
45+
setNavbarState({ type: 'closed' });
46+
}
4647
};
4748

4849
return { navbarState, toggle };

components/layout/navbar/MobileNav.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import { useState } from 'react';
55

66
import MobileNavDetail from '@/components/layout/navbar/MobileNavDetail';
77
import { main, SegmentNode } from '@/constants/segmentNode';
8-
import { useNavbarContext } from '@/contexts/NavbarContext';
9-
import { useSessionContext } from '@/contexts/SessionContext';
108
import { useRouter } from '@/i18n/routing';
9+
import { useNavbarStore } from '@/stores/NavbarStore';
10+
import { useSessionStore } from '@/stores/SessionStore';
1111
import useCurrentSegmentNode from '@/utils/hooks/useCurrentSegmentNode';
1212
import useLanguage from '@/utils/hooks/useLanguage';
1313
import { isAncestorNode } from '@/utils/page';
1414

1515
// TODO: 모바일에서 MajorCategoryPageLayout 처리
1616
export default function MobileNav() {
17-
const { navbarState } = useNavbarContext();
17+
const navbarState = useNavbarStore((s) => s.navbarState);
1818
if (navbarState.type !== 'hovered') return <></>;
1919

2020
return (
@@ -26,7 +26,8 @@ export default function MobileNav() {
2626
}
2727

2828
function MobileNavList() {
29-
const { navbarState, setNavbarState } = useNavbarContext();
29+
const navbarState = useNavbarStore((s) => s.navbarState);
30+
const setNavbarState = useNavbarStore((s) => s.setNavbarState);
3031
const [search, setSearch] = useState(false);
3132

3233
const cur = useCurrentSegmentNode();
@@ -93,7 +94,9 @@ const SearchPage = () => {
9394
};
9495

9596
const AuthButton = () => {
96-
const { state, login, logout } = useSessionContext();
97+
const state = useSessionStore((s) => s.state);
98+
const login = useSessionStore((s) => s.login);
99+
const logout = useSessionStore((s) => s.logout);
97100

98101
return (
99102
<button onClick={state === 'logout' ? login : logout} className="mt-6">

components/layout/navbar/MobileNavDetail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import NavTreeLabel from '@/components/layout/navbar/NavtreeRow';
22
import { SegmentNode } from '@/constants/segmentNode';
3-
import { useNavbarContext } from '@/contexts/NavbarContext';
3+
import { useNavbarStore } from '@/stores/NavbarStore';
44
import useCurrentSegmentNode from '@/utils/hooks/useCurrentSegmentNode';
55

66
export default function MobileNavDetail() {
7-
const { navbarState } = useNavbarContext();
7+
const navbarState = useNavbarStore((s) => s.navbarState);
88
const curNode = useCurrentSegmentNode();
99

1010
if (navbarState.type !== 'hovered') return <></>;

components/layout/navbar/Navbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import NavbarDetail from '@/components/layout/navbar/NavbarDetail';
44
import NavbarRoot from '@/components/layout/navbar/NavbarRoot';
5-
import { useNavbarContext } from '@/contexts/NavbarContext';
65
import { usePathname } from '@/i18n/routing';
6+
import { useNavbarStore } from '@/stores/NavbarStore';
77

88
// 네비바 컴포넌트들은 모두 interactivity가 크므로 CSR 처리합니다.
99
export default function Navbar() {
10-
const { setNavbarState } = useNavbarContext();
1110
const path = usePathname();
11+
const setNavbarState = useNavbarStore((state) => state.setNavbarState); // ✅ 상태 변경함수 zustand에서 직접 호출
1212

1313
const handleMouseLeave = () => {
1414
setNavbarState({ type: path === '/' ? 'expanded' : 'closed' });

0 commit comments

Comments
 (0)