행복을 담는 블로그

[TIL] 240401(월) useMutation으로 수정하기 with supabase 본문

TIL

[TIL] 240401(월) useMutation으로 수정하기 with supabase

hyun0zin 2024. 4. 2. 02:16

마이페이지에서 프로필의 정보를 수정하는 기능을 구현하였다.

마이페이지에서 프로필 정보를 수정하기 위해서 다음과 같은 작업이 필요하다.


1. supabase에서 user의 정보를 불러와서 프로필 수정 input 창에 넣어줘야 한다.

우선 supabase에서 정보를 불러오기 위해 api 호출을 진행해야 한다.

이번 프로젝트에서 api 호출을 상당히 많이 진행하기 때문에, 근본적인 의문이 생겼다.


🧐 바로 반복되는 api 호출, 과연 최적화하는 방법은 무엇인가???

(전체 데이터를 불러와서 각자 걸러서 사용 vs api를 따로 작성하여 각자 필요한 데이터만 가져오기)

1) api 도 각자 필요한 값이 다 다른데, select(”*”) : 모든데이터 불러오기를 해서 한 번에 여러곳에서 동일한 호출함수 사용하기
→ 단점 : 필요없는 데이터도 같이 여러번 호출됨
2) 각자 필요한 api 호출 함수를 각자 따로 만들어서 필요한 곳에서 호출하여 사용하기

=> 튜터님의 답변은 컴포넌트의 재사용성면에서 각자 api를 호출하는게 더 낫다! 였다.
컴포넌트가 독립적으로 작동할 수 있도록 각 컴포넌트 별로 api 호출을 하는게 낫고, 컴포넌트 간 같은 api 호출을 사용하면 의존성이 높아져서 유지보수에 어렵다는 답변!!

아무튼 다시 api 호출을 하는 코드를 살펴보자.

// User(선생님/수강생) 정보 불러오기
export const getUserInfo = async () => {
  const { data: userInfo, error }: PostgrestMaybeSingleResponse<UserType> = await supabase
    .from('user')
    .select('nickname, email, password, profile_image')
    .eq('user_id', userId)
    .single();

  if (error) {
    console.error(error);
  }

  return userInfo;
};

▶️ user table에서 nickname, email, password, profile_image 이 값들만 선택해서 가져오는데, user_id와 현재 로그인한 userId가 같은 값만 가져와라.
= 즉, 현재 로그인한 user의 정보 중에서 내가 필요한 값만 select 해서 가지고 오자! 이 말이다.

이후, user 정보가 필요한 컴포넌트에서 useQuery를 통해 data를 불러온다.

// useQuery를 통해 userInfo 정보를 불러온다.
const { data: userInfo, isPending } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => getUserInfo()
  });

// 초기 값에 불러온 `userInfo.nickname` 값을 넣어준다. 
  const [newNickname, setNewNickname] = useState('');
  const [isEditing, setIsEditing] = useState(false); // 수정된 사항 확인 여부

  useEffect(() => {
    if (userInfo) {
      setNewNickname(userInfo.nickname || '');
    }
  }, [userInfo]);



2. 수정한 프로필 정보가 다시 supabase에 update 되어야 한다.

data를 수정하기 위해서는 useMutation을 활용해보자.

  // 닉네임 수정
  const handleOnChangeNickname = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const newNickname = e.target.value;
    setNewNickname(newNickname); // 새로 작성한 닉네임

    const isAvailable = !(await checkUserNickname({ newNickname }));
    setIsAvailableNickname(isAvailable); // 중복 여부 상태 업데이트

    // 이미 존재하는 닉네임을 입력한 경우 수정 완료 버튼 비활성화
    setIsActiveBtn(!isAvailable);
  };

  // 유저 정보 수정하기 : useMutation
  const { mutate: updateUserInfoMutation } = useMutation({
    mutationFn: ({ newNickname }: UpdateUserInfoType) => updateUserInfo({ newNickname }),
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['user']
      });
      setIsEditing(false);
    }
  });

이후 supabase에 해당 정보를 update를 해주어야 한다.

// User(선생님/수강생) 정보 수정하기 : supabase에 update
export const updateUserInfo = async ({ newNickname }: UpdateUserInfoType) => {
  const { data, error } = await supabase.from('user').update({ nickname: newNickname }).eq('user_id', userId);
  if (error) {
    console.error(error);
  }
  return data;
};



3. 닉네임이 중복되지는 않는지 확인해준다.

마지막으로 닉네임이 중복되는지를 확인해주어야 하는데, 그러기 위해서는 user의 nickname이 어떤 것들이 있는지 비교해야한다.

// User(선생님/수강생) nickname 중복 확인하기
export const checkUserNickname = async ({ newNickname }: Pick<UpdateUserInfoType, 'newNickname'>) => {
  const { data, error } = await supabase
    .from('user')
    .select('nickname')
    .not('user_id', 'eq', userId)
    .eq('nickname', newNickname);
  if (error) {
    console.error(error.message);
    return false;
  }

  return data.length > 0;
};

▶️ 바로 not 을 이용하면, 현재 나의 user_id는 제외하고 다른 user_id의 값들 중에서 nickname이 newNickname(내가 수정한 닉네임)과 같냐? 라는 말을 묻고 있다.

이때 data.length가 0보다 크면, 즉, 조회된 data의 배열이 하나 이상이라는 말은 중복된 data가 있다는 말이므로, 그때는 true를 반환한다!


// 닉네임 중복 여부를 나타내는 상태와 해당 상태를 업데이트하는 함수
const [isAvailableNickname, setIsAvailableNickname] = useState(true); // 닉네임 중복 여부 상태 업데이트

// 새로운 닉네임이 이미 사용 중인지를 확인하는 비동기 함수를 호출 -> isAvailable에 결과 저장
// true : 중복되지 않은 닉네임일 경우 / false : 중복된 닉네임일 경우
 const isAvailable = !(await checkUserNickname({ newNickname })); 
    setIsAvailableNickname(isAvailable); // 중복 여부 상태 업데이트 

// true (중복 x) : 아무것도 반환하지 않는다. / false (중복된 닉네임) : 메세지 보여주기  
return(
    {isAvailableNickname ? (
      ''
      ) : (
      <p className="font-thin p-2">이미 사용중인 닉네임입니다. 다른 닉네임을 입력해주세요.</p>
    )}
)

▶️ 중복된 닉네임이 존재하는 경우, isAvailable 값은 true가 되므로 !(true)는 false가 된다. 이는 이미 사용 중인 닉네임이라는 것을 의미함!!


이렇게 api 호출(select) -> useMutation (수정) -> 다시 update 과정을 거쳐 정보를 불러와서 수정하는 방법을 거치게 된다.
또한, 관계형 데이터베이스인 supabase를 통해 데이터를 보다 쉽게 데이터에 접근할 수 있고, 열과 행으로 이루어져 편하게 원하는 데이터에 접근할 수 있다는 장점이 있다.
SQL 과 noSQL에 대해서 조금 더 공부를 해보아야겠다... 끝이 없는 공부의 세계란... 재밌다...(?)