글쓰는쿼카의 PM 여정
[FP] Context API 로 유저 상태 관리하기 (2024. 7. 25.) 본문
#스파르타코딩클럽 #내일배움캠프(프론트엔드_React)
학습주제 : Next.js - route handler, Context API(useContext)
학습내용 : useContext 초기세팅, 유저 상태 관리 오류 처리
학습일 : 2024. 7. 25.
배운 내용 요약
Context API 협업 프로젝트에 적용하기
1. 유저 상태 관리가 왜 필요할까?
유저의 로그인 상태에 따라 헤더의 오른쪽 끝에 변화를 주고 싶었다.
(👇🏻로그인 유무에 따른 헤더 변화)
- 로그인 할 경우 : (좌) 배지+유저닉네임 (우) 로그아웃 버튼
- 로그아웃 할 경우 : (단독) 로그인 버튼
또, 어떤 페이지에서든 로그인 한 유저 정보('users' 테이블의 userId, nickname, total_point 값)를 사용할 수 있었으면 했다.
(👇🏻 로그인 유저 정보를 활용할 페이지)
- 공통 레이아웃 : 헤더 우측 버튼
- 메인페이지 : 오늘의퀴즈 문제풀기 제한(하루에 한 문제만 풀 수 있음)
- 짠 소비 구경 & 짠 노하우 : 게시글 작성하기, 게시글의 상세페이지의 댓글 작성/수정/삭제, (짠 소비 구경: 투표)
- 살까&말까? Live : 유저일 경우 라이브 채팅방 입장하기 가능
- 마이페이지 : 회원 정보 변경, 누적 포인트, 내 등급, 내가 쓴 글 확인하기, 좋아요 누른 글 확인하기
2. 왜 useContext를 사용할까?
프론트엔드에서 유저의 상태를 관리하는 방법은 Zustand 와 Context API 로 나뉜다.
(👇🏻chatGPT 가 알려주는 Zustand 와 Context API의 장단점)

Next.js에서 Zustand과 Context API를 사용한 유저 상태 관리 장단점 비교
Next.js 환경에서 유저 상태를 관리하기 위해 Zustand과 Context API를 사용할 때 각각의 장단점을 비교해 보겠습니다.
Zustand
장점:
- 간결한 API: Zustand은 간결하고 직관적인 API를 제공하여 상태 관리 로직을 쉽게 작성할 수 있습니다.
- TypeScript 지원: TypeScript와의 호환성이 뛰어나 안전하고 유형이 안전한 코드를 작성할 수 있습니다.
- Immer 적용: Immer를 기반으로 불변성을 유지하여 예측 가능하고 안정적인 상태 관리를 가능하게 합니다.
- 커스텀 미들웨어: 미들웨어를 통해 상태 업데이트 전후에 추가적인 로직을 실행할 수 있습니다.
- React DevTools 지원: React DevTools를 통해 상태 변경을 시각적으로 확인하고 디버깅할 수 있습니다.
단점:
- 학습 곡선: Context API에 비해 다소 학습 곡선이 있을 수 있습니다.
- 커뮤니티: Context API에 비해 커뮤니티가 작아 정보를 찾기가 어려울 수 있습니다.
Context API
장점:
- React 기본 기능: React의 기본 기능으로, 별도의 라이브러리를 설치할 필요가 없습니다.
- 유연성: 다양한 방식으로 상태를 관리할 수 있는 유연성을 제공합니다.
- 넓은 커뮤니티: 많은 개발자들이 사용하고 있어 다양한 정보와 예제를 쉽게 찾을 수 있습니다.
단점:
- 보일러플레이트 코드: 상태 관리를 위한 코드가 반복적이고冗長할 수 있습니다.
- 복잡한 상태 관리: 상태가 복잡해질수록 코드 관리가 어려워질 수 있습니다.
- 불변성: 상태를 직접 수정할 수 있어 의도하지 않은 상태 변화가 발생할 가능성이 있습니다.
이번 프로젝트의 경우~~
따로 라이브러리 개별 설치가 필요 없고, 비교적 유연한 Context API를 선택했다.
(👇🏻Context API 를 통해 얻고 싶은 것)
- 유저의 로그인 상태 관리 (정확히는 로컬 컴퓨터에 쿠키 저장 여부)
- 유저의 고유한 아이디값인 userId ( supabase 데이터베이스의 테이블에서 해당 userId와 일치한 회원정보를 마음대로 꺼내쓸 수 있음)
Context API 코드 짜기
1. Context API 초기 세팅을 해봅시다
1) 초기 세팅
- provider 폴더에 UserProvider 파일 생성 : createContext 생성 후 export, UserProvider 함수 export default 등
- app layout 파일에 UserProvide로 children 감싸기
→ 추가: 조금 더 깔끔하게 하고 싶다면 Provider 컴포넌트를 하나 만들고, Provider 컴포넌트 안에서 UserProvider를 감싸면 훨~씬 보기 좋다. (아래 사진 참고)
// src > app > layout.tsx
...
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>{children}</Providers> ⭐⭐⭐(여기!)
</body>
</html>
);
}
// src > provider > Provider.tsx
...
function Providers({ children }: { children: ReactNode }) {
return (
<QueryProvider>
<UserProvider> ⭐⭐⭐(여기!)
<ModalProvider>{children}</ModalProvider>
</UserProvider>⭐⭐⭐
</QueryProvider>
);
}
2. Context Provider(UserProvider) 작성해봅시다
1) logIn 함수 작성
- 라우트핸들러 사용 (예: api 폴더에 있는 getuser 함수)
- 유저의 email, password를 함수의 인자로 받고, 함수 자체를 provider의 value로 보내서 context 안에 logIn 함수를 가져다 쓰게 만듦
2) useState : 유저 정보 저장 및 꺼내기 기능
3) useEffect : 유저 정보 유지 (**관련 이슈는 아래 자세하게 기록함)
(👇🏻UserProvider 전체 코드 보기)
// src > provider > contexts > userContext.tsx
...
const UserProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null); ⭐⭐⭐(여기!!)
useEffect(() => { ⭐⭐⭐(여기!!)
// 유저 정보 가오기
(async () => {
const response = await fetch("http://localhost:3000/api/auth/me");
const data = await response.json();
const users = data.users;
if (users) {
setUser(users);
} else {
setUser(null);
console.log(data.error);
}
})();
}, []);
const logIn = async (email: string, password: string) => { ⭐⭐⭐(여기!!)
const response = await fetch("http://localhost:3000/api/auth/login", {
method: "POST",
body: JSON.stringify({ email, password })
});
if (!response.ok) {
console.log("로그인 실패");
}
};
return <UserContext.Provider value={{ user, logIn }}>{children}</UserContext.Provider>;
};
export default UserProvider;
3. Context API 활용하기
(👇🏻HeaderContainer, LoginContainer 코드 보기)
// src > app > (main) > _components > HeaderContainer.tsx
import { useUserContext } from "@/provider/contexts/userContext";
...
function HeaderContainer() {
const { user } = useUserContext();
// src > app > (main) > _components > HeaderContainer.tsx
import { useUserContext } from "@/provider/contexts/userContext";
...
const { logIn } = useUserContext();
const handleClickLogin: MouseEventHandler<HTMLButtonElement> = async (e) => {
e.preventDefault();
logIn(email, password);
router.replace("/");
};
4. 오류... 오류가... 났어요...🥲
- 요약: 로그인 후 메인페이지로 돌아왔을 때는 로그인 상태가 유지되지만 메인페이지에서 새로고침할 경우 로그인 상태가 사라지는 이슈가 있었음.
- 문제: 기존 라우트 핸들러도 함께 사용해서 어디서 오류가 나고 있는지, 새로고침을 해도 로그인 상태를 유지하려면 어디서 어떻게 잡아야 하는지 몰라 시간이 많이 지체 됨.
- 해결: getUser 라우트 핸들러에서 바로 users 테이블의 userId와 동일한 유저의 정보를 전달(return)하였고, UserProvider에서 setter 함수로 전달받은 유저를 저장하였으며, useContext를 통해 헤더컴포넌트에서 유저 정보를 호출하고 useEffect로 새로고침할 때마다 그 유저 정보가 그대로 유지할 수 있도록 하였음🥹
'개발 > Next.js' 카테고리의 다른 글
[FP] 마이페이지의 닉네임 변경하기 (2024. 7. 29.) (0) | 2024.07.29 |
---|---|
🐤[베이직] Next.js - 간단하게 훑기 (2024. 6. 28.) (0) | 2024.06.28 |