팀프로젝트_PetHarmony

🌈 컴포넌트 간 UI 동기화

이채림 2024. 8. 23. 03:50

[마이페이지]를 구현하고 있다.

[마이페이지] > [내 정보 수정]에서 이름을 수정해보자.

"이승재" 라는 사용자가 이름을 "이백억"으로 바꾸고 수정 완료를 눌렀지만

좌측 상단에 있는 [MY INFO] 이름값과 최상단 [헤더] 이름값이 바뀌지 않았다.

 

🧐 [내 정보 수정]에서 값을 변경할 때, [MY INFO]나 [헤더]와 같은 컴포넌트에서 즉시 값이 반영되도록 하고 싶다면 어떻게 해야할까?

1. 새로고침을 한다.
2. props로 상태를 전달한다.

사실 저번 프로젝트 <Ziczone>에서는 새로고침을 하는 방법도 썻었다.

하지만 이렇게 하면 전체 페이지를 다시 로드하므로, 불필요하게 리소스를 소비하게 된다.

또한 UX 측면에서 새로고침은 지연을 초래할 수 있어 매끄럽지 않다.

 

props를 사용하는 방식의 장점

- 상태 변화가 필요한 여러 컴포넌트에서 일관되게 데이터를 관리할 수 있다.
- React의 특성을 활용하여 효율적이고 성능 좋은 업데이트가 가능하다.
- 유지보수가 용이하고 확장 가능성이 높다. 새로운 컴포넌트가 추가되더라도 간단히 상태를 공유할 수 있다.

 

구현방법

1. 상위 컴포넌트에서 상태를 관리한다.

2. 상태 변경 함수를 전달한다.

 

컴포넌트명

[헤더] : Header.jsx
[MY INFO] : MyProfile.jsx
[내 정보 수정] : ProfileEdit.jsx
[My INFO], [내 정보 수정]의 공통 부모 컴포넌트 : MyPage.jsx

현재 컴포넌트 구조를 반영해서

MyPage 컴포넌트에서 상태를 관리하고,

Header는 별도의 레이아웃 컴포넌트이므로 전역 상태 관리(Zustand)를 통해 값이 변경되면 자동으로 반영되도록 구현하면 된다.

 

아래는 전체 코드이다.

 

src/mypage/components/MyPage.jsx

const MyPage = () => {
    // Zustand를 사용하여 토큰을 가져옴
    const { token } = useAuthStore();
    // 공통 상태 관리
    const [profile, setProfile] = useState({
        userName: "",
        email: "",
        phone: ""
    });
    // 프로필 정보를 가져오기 위한 비동기 함수
    useEffect(() => {
        const fetchMyProfile = async () => {
            try {
                const response = await axios.get('http://localhost:8080/api/user/myProfile', {
                    headers: {
                        Authorization: `Bearer ${token}`
                    },
                });
                if (response.status === 200) {
                    setProfile(response.data);
                }
            } catch (error) {
                console.error("요청이 실패했습니다.", error);
            }
        };
        fetchMyProfile();
    }, [token]);

    return (
        <div className="my_page">
            <div className="mp_left">
                {/* profile props로 전달 */}
                <MyProfile profile={profile} />
                <SideBar />
            </div>
            <div className="mp_right">
                <Routes>
                    {/* 기본 경로로 렌더링하며 profile, setProfile props로 전달 */}
                    <Route index element={<ProfileEdit token={token} profile={profile} setProfile={setProfile} />} />
                    {/* 특정 경로에 대해 각 컴포넌트 렌더링 */}
                    <Route path="profile-edit" element={<ProfileEdit token={token} profile={profile} setProfile={setProfile} />} />
                    <Route path="password-edit" element={<PasswordEdit />} />
                    <Route path="interested-pets" element={<InterestedPets />} />
                    <Route path="pin-posts" element={<PinPosts />} />
                    <Route path="my-comments" element={<MyComments />} />
                    <Route path="my-posts" element={<MyPosts />} />
                    <Route path="delete-account" element={<DeleteAccount />} />
                </Routes>
            </div>
        </div>
    );
};

 

src/components/dashboard/ProfileEdit.jsx

const ProfileEdit = ({ token, profile, setProfile }) => {
    // Zustand의 setName 함수를 통해 전역 상태의 이름 업데이트하는 함수 가져옴
    const { setName: updateGlobalName } = useAuthStore();
    // profile에서 초기 상태 설정
    const [name, setName] = useState(profile.userName);
    const [email, setEmail] = useState(profile.email);
    const [phone, setPhone] = useState(profile.phone);

    // 프로필 정보가 업데이트 될 때마다 업데이트
    useEffect(() => {
        setName(profile.userName);
        setEmail(profile.email);
        setPhone(profile.phone);
    }, [profile]);
    // 이름 입력 필드의 값이 변경될 때 상태 업데이트
    const handleNameChange = (e) => {
        setName(e.target.value);
    };
    // 이메일 입력 필드의 값이 변경될 때 상태 업데이트
    const handleEmailChange = (e) => {
        setEmail(e.target.value);
    };
    // 전화번호 입력 필드의 값이 변경될 때 상태 업데이트
    const handlePhoneChange = (e) => {
        setPhone(e.target.value);
    };
    // 프로필 업데이트를 서버에 제출하는 함수
    const handleProfileEditSubmit = async () => {
        try {
            const response = await axios.put('http://localhost:8080/api/user/myProfile', {
                userName: name,
                email: email,
                phone: phone,
            }, {
                headers: {
                    Authorization: `Bearer ${token}`
                }
            });

            if (response.status === 200) {
                updateGlobalName(name);    // 이름을 전역 상태에 업데이트 (헤더)
                setProfile(response.data); // 프로필 정보 업데이트
            }
        } catch (error) {
            console.error('내 정보 수정을 실패했습니다.', error);
        }
    };

    return (
        <div className="my_edit">
            <p className="me_title">내 정보 수정</p>
            <div className="me_content">
                <InputField
                    icon={editNameIcon}
                    type="text"
                    placeholder=""
                    value={name}
                    onChange={handleNameChange}
                />
                <InputField
                    icon={editEmailIcon}
                    type="email"
                    placeholder=""
                    value={email}
                    onChange={handleEmailChange}
                />
                <InputField
                    icon={editPhoneIcon}
                    type="text"
                    placeholder=""
                    value={phone}
                    onChange={handlePhoneChange}
                />
            </div>
            <button className="me_btn" onClick={handleProfileEditSubmit}>수정 완료</button>
        </div>
    );
}

 

src/mypage/components/profile/MyProfile.jsx

const MyProfile = ({ profile }) => {
    return (
        <div className="my_profile">
            <p className="mp_title">MY INFO</p>
            <div className="mp_content">
                <div className="mp_content_item">
                    <img className="mp_nameIcon" src={nameIcon} alt="" />
                    <p>{profile.userName}</p>
                </div>
                <div className="mp_content_item">
                    <img className="mp_emailIcon" src={emailIcon} alt="" />
                    <p>{profile.email}</p>
                </div>
                <div className="mp_content_item">
                    <img className="mp_phoneIcon" src={phoneIcon} alt="" />
                    <p>{profile.phone}</p>
                </div>
            </div>
        </div>
    );
}

 

src/store/useAuthStore.js

import { create } from 'zustand';

const useAuthStore = create((set) => ({
  isLogin: false,
  token: '',
  email: '',
  name: '',
  role: '',

  login: (token, email, name, role) => set({
    isLogin: true,
    token,
    email,
    name,
    role
  }),
  logout: () => set({
    isLogin: false,
    token: '',
    email: '',
    name: '',
    role: ''
  }),
  // [헤더] > OOO님
  setName: (newName) => set({ name: newName })
}));

export default useAuthStore;

 

코드를 설명하자면,

 

사용자가 프로필을 수정할 때, 수정된 프로필 정보가 서버로 전송되고, 서버 응답이 성공적이면, 수정된 이름이 Zustand의 setName을 통해 글로벌 상태로 업데이트 된다. 이로 인해 헤더에 표시되는 이름도 함께 업데이트 된다.

또한 useEffect에서 Zustand에서 가져온 token을 사용해 서버로부터 사용자 프로필을 가져와서 UI가 즉시 업데이트된다.