오늘의 기능구현은 프로필 수정.
작업의 큰 흐름은 이렇다.
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
에 담아 관리했다.
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'로 변경
});
이 부분 때문에 초반에 문제가 발생했다.
일단 users 테이블에 업데이트 후 업데이트 된 데이터를 반환받아 사용했는데 supabase가 반환하는 데이터는 [ { ... } ]
와 같은 배열 형태였어서, [0]
인덱스를 표기해줘야 했다.
또, 후에 updateAuthUser
함수에서 const updatedUser = { ...state.authUser, ...updates };
로 기존 로컬스토리지의 사용자 데이터에 새로 업데이트 된 사용자 정보를 붙이는데 둘이 key값이 달라서 userName : 닉네임 , user_name: 업데이트된 닉네임
과 같이 들어갔다.
key값이 다르다거나, 데이터 형식이 다르다거나, 구조분해할당이 잘못 되는 에러를 참 많이 겪는다...^^ 신경쓴다고 하는데도 늘 한번씩 걸림.
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, 바꿀 데이터들을 받는다.
const updatedData = {
user_name: userName,
profile_image: profileImage,
};
데이터를 묶어서 .update()에 넣어줘도 여러 행 업데이트가 가능하고, id값 대신 특정 조건이 여러 행과 일치하면 다수의 행도 수정된다.
지금은 유니크한 유저의 2개의 행만 업데이트 한다지만, 많은 데이터를 한꺼번에 업데이트 할 경우 성능 신경을 써야겠지...
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();
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에 즉시 반영되는 것을 확인할 수 있다.