내일배움캠프 React_7기 TIL - 36. 아웃소싱 프로젝트 중간 기록

·2024년 12월 3일
0

오늘의 기능구현은 프로필 수정.

작업의 큰 흐름은 이렇다.

  1. 사용자에게 데이터 입력 받음
  2. supabase의 users 테이블에 Update
  3. zustand persist로 저장되어있던 로컬 스토리지의 유저 상태도 업데이트하여, 변경 시 UI에 즉시 반영되도록 함.

MypageComponent

  const authUser = useAuthUserStore((state) => state.authUser); //로그인된 사용자 정보 가져옴
  const { logout } = useAuth(); //로그아웃 기능


  const {
    newUserName,
    setNewUserName,
    newProfileImage,
    setNewProfileImage,
    handleProfileUpdate,
  } = useProfileUpdate(); //훅으로 프로필 업데이트 로직 관리



  const handleSignButtonClick = () => {
    if (authUser) {
      handleProfileUpdate();
		...
    } else {
      navigate('/login');
    }
  }; //로그인된 사용자 있으면 프로필 수정 로직 실행, 아니면 로그인 페이지로
    
  return (
    <MyPageWrap>
      <MyPageTitle>
        <MyPageUser>
          {authUser ? (
            <UserProfileDiv
              $backgroundUrl={authUser.profileImage}
            ></UserProfileDiv>
          ) : (
            <i className="fa-solid fa-circle-user"></i>
          )}
        </MyPageUser>
        <TitleP>
          {authUser?.userName ? `${authUser.userName}` : '로그인해주세요'}
        </TitleP>
      </MyPageTitle>
      {authUser ? (
        <>
          <LogoutBtn onClick={logout}>
            <i className="fa-solid fa-arrow-right-from-bracket"></i>
          </LogoutBtn>
          <Input
            $isMyPage="true"
            type="text"
            placeholder={authUser.userName}
            value={newUserName}
            onChange={(e) => setNewUserName(e.target.value)}
          />
          <TitleP>프로필사진</TitleP>
          <FileUploadWrap>
            <FileLabel htmlFor="file-input">파일 선택</FileLabel>
            <FileInput
              id="file-input"
              type="file"
              accept="image/*"
              onChange={(e) => setNewProfileImage(e.target.files[0])}
            />
            <FileName>
              {newProfileImage
                ? `선택된 파일: ${newProfileImage.name}`
                : '선택된 파일 없음'}
            </FileName>
          </FileUploadWrap>
        </>
      ) : (
        <></>
      )}
      <SignButton $isMyPage="true" onClick={handleSignButtonClick}>
        {authUser ? '수정' : '로그인'}
      </SignButton>
    </MyPageWrap>
  );
};

export default MyPageComponent;
  

프로필 업데이트 관련 로직은 useProfileUpdate 에 담아 관리했다.

useProfileUpdate

import { useState } from 'react';
import { uploadFile } from '../utils/uploadFile'; //스토리지에 이미지 파일 업로드 기능
import { updateUserProfile } from '../api/user/profile';
import useAuthUserStore from '../stores/useAuthUserStore';

const useProfileUpdate = () => {
  const { authUser, updateAuthUser } = useAuthUserStore(); //로그인 된 사용자 정보, 로컬스토리지의 사용자 정보 업데이트 기능 가져옴
  
  //업데이트 할 데이터 상태로 관리
  const [newUserName, setNewUserName] = useState('');
  const [newProfileImage, setNewProfileImage] = useState(null);

  // 프로필 업데이트 함수
  const handleProfileUpdate = async () => {
    try {
      let profileImageUrl = null;

      // 프로필 이미지가 있을 경우 업로드
      if (newProfileImage) {
        profileImageUrl = await uploadFile('profile_images', newProfileImage);
      }

      // 닉네임과 프로필 이미지 업데이트
      const data = await updateUserProfile({
        id: authUser.id,
        userName: newUserName || authUser.userName,
        profileImage: profileImageUrl || authUser.profileImage,
      });

      console.log('프로필이 성공적으로 업데이트되었습니다.', data);
      
      updateAuthUser({
        id: data[0].id,
        email: authUser.email,
        userName: data[0].user_name, // 'user_name'을 'userName'으로 변경
        profileImage: data[0].profile_image, // 'profile_image'를 'profileImage'로 변경
      });
    } catch (error) {
      console.error('프로필 업데이트 실패:', error); // 오류 처리
    }
  };

  return {
    newUserName,
    setNewUserName,
    newProfileImage,
    setNewProfileImage,
    handleProfileUpdate,
  };
};

export default useProfileUpdate;
 updateAuthUser({
        id: data[0].id,
        email: authUser.email,
        userName: data[0].user_name, // 'user_name'을 'userName'으로 변경
        profileImage: data[0].profile_image, // 'profile_image'를 'profileImage'로 변경
      });

이 부분 때문에 초반에 문제가 발생했다.

1. supabase가 반환하는 데이터 형식 문제

일단 users 테이블에 업데이트 후 업데이트 된 데이터를 반환받아 사용했는데 supabase가 반환하는 데이터는 [ { ... } ]와 같은 배열 형태였어서, [0] 인덱스를 표기해줘야 했다.

2. 업데이트 시 Key값 상이

또, 후에 updateAuthUser 함수에서 const updatedUser = { ...state.authUser, ...updates };로 기존 로컬스토리지의 사용자 데이터에 새로 업데이트 된 사용자 정보를 붙이는데 둘이 key값이 달라서 userName : 닉네임 , user_name: 업데이트된 닉네임 과 같이 들어갔다.

key값이 다르다거나, 데이터 형식이 다르다거나, 구조분해할당이 잘못 되는 에러를 참 많이 겪는다...^^ 신경쓴다고 하는데도 늘 한번씩 걸림.

updateUserProfile

import { supabase } from '../supabase/supabase'; // supabase 설정 가져오기

// 사용자 프로필 업데이트 함수
export const updateUserProfile = async ({ id, userName, profileImage }) => {
  console.log('Received values:', id, userName, profileImage);
  // 업데이트할 데이터 준비
  const updatedData = {
    user_name: userName,
    profile_image: profileImage,
  };

  // 데이터베이스 업데이트
  const { data, error } = await supabase
    .from('users')
    .update(updatedData)
    .eq('id', id)
    .select(); //

  // 에러 발생 시 처리
  if (error) {
    throw new Error('프로필 업데이트에 실패했습니다: ' + error.message);
  }

  return data; // 업데이트된 데이터 반환 (필요 시)
};

users 테이블에 해당 유저 데이터를 수정하는 함수이다. 사용자id, 바꿀 데이터들을 받는다.

1. .update() 메서드는 단일 행뿐만 아니라 조건에 맞는 여러 행도 동시에 수정할 수 있다

 const updatedData = {
    user_name: userName,
    profile_image: profileImage,
  };

데이터를 묶어서 .update()에 넣어줘도 여러 행 업데이트가 가능하고, id값 대신 특정 조건이 여러 행과 일치하면 다수의 행도 수정된다.
지금은 유니크한 유저의 2개의 행만 업데이트 한다지만, 많은 데이터를 한꺼번에 업데이트 할 경우 성능 신경을 써야겠지...

2. .update() 메서드는 기본적으로 데이터를 리턴하지 않는다.

const { data, error } = await supabase
  .from('users')
  .update({ user_name: '새 이름' })
  .eq('id', '123');

처음엔 이런 코드로, 업데이트 된 데이터를 리턴받고자 했다.
하지만 콘솔로 찍어보니 null 값만 찍혔다. 알고보니 .update() 메서드는 기본적으로 데이터를 리턴하지 않기 때문에, .select() 메서드를 체이닝하여 업데이트 된 행의 데이터 반환을 명시해야 했다.

수정 후

const { data, error } = await supabase
  .from('users')
  .update({ user_name: '새 이름' })
  .eq('id', '123')
  .select();

useAuthUserStore (Zustand)

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useAuthUserStore = create(
  persist(
    (set) => ({
      authUser: null,
		...
      updateAuthUser: (updates) =>
        set((state) => {
          console.log('붙는 데이터', updates);
          const updatedUser = { ...state.authUser, ...updates };
          console.log('updateAuthUser data:', updatedUser);
          return { authUser: updatedUser };
        }),
    }),
    {
      name: 'auth-storage',
    },
  ),
);

export default useAuthUserStore;

updateAuthUser 함수를 추가해서, 테이블의 유저 정보가 성공적으로 업데이트 되면 로컬스토리지에 저장된 사용자 정보도 업데이트하고자 했다.
이 함수는 updates 객체를 받아서, 현재 authUser 상태에 새로운 데이터를 병합하는 방식이다.
set을 호출하여 authUser 상태를 새로 업데이트하는데, 이때 기존의 authUser 값과 updates를 병합하여 updatedUser 객체를 만들고, 그 값을 상태로 저장한다.
업데이트 후에 updateAuthUser를 실행하면, 프로필 변경 후에도 UI에 즉시 반영되는 것을 확인할 수 있다.

profile
내배캠 React_7기 이수중

0개의 댓글

관련 채용 정보