글쓰는쿼카의 PM 여정

[FP] Context API 로 유저 상태 관리하기 (2024. 7. 25.) 본문

개발/Next.js

[FP] Context API 로 유저 상태 관리하기 (2024. 7. 25.)

글쓰는쿼카 joymet33 2024. 7. 25. 23:45

#스파르타코딩클럽 #내일배움캠프(프론트엔드_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로 새로고침할 때마다 그 유저 정보가 그대로 유지할 수 있도록 하였음🥹