팀프로젝트를 하면서 로그인, 회원가입을 추가하는게 좋을 것 같았다.
회원만 지도를 활용해서 생성할 수 있도록 하고 비회원은 간단한 검색기능만 사용하도록 하면 조금 더 좋지 않을까... 하는 생각..!
개인과제에서 짜둔 로그인, 회원가입 코드를 이번 기회에 Zustand로 꼭 만들어내고 싶었다.
그래서 바아로 스탠다드반 수업 강의도 다시 보고 강의 자료도 다시 들여다보면서 조금씩 바꿔보았다.
Zustand로 회원가입, 로그인 구현하기
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useAuthStore = create(
persist(
(set) => ({
accessToken: "",
avatar: "",
nickname: "",
success: false,
userId: "",
isLoggedIn: false,
setAccessToken: (token) => set({ accessToken: token }),
setAvatar: (avatar) => set({ avatar }),
setNickname: (nickname) => set({ nickname }),
setSuccess: (success) => set({ success }),
setUserId: (userId) => set({ userId }),
setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }),
}),
{
name: "auth-storage", // 저장소 이름
getStorage: () => localStorage, // 로컬 스토리지 사용
}
)
);
export default useAuthStore;
이 코드를 기반으로 개인과제로 만들었던 로그인, 회원가입 코드를 리팩토링해서 팀프로젝트 코드에 활용해볼 생각이다!
//설치 드가자고 ✨
yarn add zustand
// src > zustand > bearsStore.js
import { create } from "zustand";
const useBearsStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
export default useBearsStore;
// src > App.jsx
import "./App.css";
import useBearsStore from "./zustand/bearsStore";
function App() {
const bears = useBearsStore((state) => state.bears);
const increasePopulation = useBearsStore((state) => state.increasePopulation);
return (
<div>
<h1>{bears} around here ...</h1>
<button onClick={increasePopulation}>one up</button>
</div>
);
}
export default App;
zustand
를 활용한 Store
를 만들어서 전역상태관리를 할 코드 작성하기Store
를 import
하고 코드 수정하기// authStore.js
import { create } from "zustand";
import { persist } from "zustand/middleware"; // 새로고침 시 그대로 유지
import { register, login as loginAPI } from "../api/auth"; // 로그인, 회원가입 함수
const useAuthStore = create(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
// 로그인 함수
login: async (formData) => {
try {
const response = await loginAPI({
id: formData.userId,
password: formData.password
});
if (response.accessToken) {
set({
token: response.accessToken,
user: response.user, // 서버 응답에서 유저 데이터 설정
isAuthenticated: true
});
} else {
alert("로그인에 실패했습니다.");
}
} catch (error) {
console.error("로그인 실패:", error);
alert("로그인에 실패했습니다. 다시 시도해주세요.");
}
},
// 회원가입 함수
signup: async (formData) => {
try {
const response = await register({
id: formData.userId,
password: formData.password,
nickname: formData.nickname
});
if (response.success) {
alert("회원가입이 완료되었습니다. 로그인 페이지로 이동합니다.");
} else {
alert("회원가입에 실패했습니다.");
}
} catch (error) {
console.error("회원가입 실패:", error);
alert("회원가입에 실패했습니다. 다시 시도해주세요.");
}
},
// 로그아웃 함수
logout: () => {
set({ user: null, token: null, isAuthenticated: false });
}
}),
{
name: "auth-storage"
}
)
);
export default useAuthStore;
코드가 조금 길게 보이는데 사실 try
, catch
문, 조건문
을 빼고나면 정말 간단하게 구현했다.
// 🚧 내가 수정한 코드
const useAuthStore = create(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
// 🔥 스탠다드반 코드
const useAuthStore = create(
persist(
(set) => ({
accessToken: "",
avatar: "",
nickname: "",
success: false,
userId: "",
isLoggedIn: false,
zustand
는 useStore
훅을 사용해 상태를 관리하는 방식을 채택하고 있어 이를 활용했다.
create
, set
, 그리고 새로고침이 되어도 로그인이 풀리지 않도록 persist
미들웨어를 사용해서 자동으로 localStorage
에 저장되도록 했다.
// 🚧 내가 수정한 코드 (로그인)
login: async (formData) => {
try {
const response = await loginAPI({
id: formData.userId,
password: formData.password
});
if (response.accessToken) {
set({
token: response.accessToken,
user: response.user, // 서버 응답에서 유저 데이터 설정
isAuthenticated: true
});
} else {
alert("로그인에 실패했습니다.");
}
} catch (error) {
console.error("로그인 실패:", error);
alert("로그인에 실패했습니다. 다시 시도해주세요.");
}
},
// 🔥 스탠다드반 코드
setAccessToken: (token) => set({ accessToken: token }),
setAvatar: (avatar) => set({ avatar }),
setNickname: (nickname) => set({ nickname }),
setSuccess: (success) => set({ success }),
setUserId: (userId) => set({ userId }),
setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }),
}),
// 🚧 스탠다드반이랑 비슷하게 코드 지워보고 비교
login: async (formData) => {
if (response.accessToken) {
set({
token: response.accessToken,
user: response.user,
isAuthenticated: true
});
}
},
처음에는 스탠다드반 참고한 코드랑 너무 다르게 보여서 막막했는데 일단 주먹구구식으로 기존에 작성했던 로그인 함수를 살짝씩 수정해서 넣어보니 구현이 되었다.
스탠다드반에서 만든 코드는 state
값을 넣어준 것 같고 나는 api를 불러오고 함수로 처리하는 과정이라 다르게 느껴진 것 같다. 그래도 어디에서든 login
함수를 뽑아서 쓸 수 있고, setAccessToken
을 사용할 수 있다는 부분은 동일하다.
{
name: "auth-storage"
}
name
은 localStorage
에 저장될 키 이름이다.
//Login.jsx
import { Link } from "react-router-dom";
import AuthForm from "../components/AuthForm";
import useAuthStore from "../stores/authStore"; //zustand store 가져오기
const Login = () => {
const login = useAuthStore((state) => state.login); //zustand 코드 적용 👏
const handleLogin = async (formData) => {
await login(formData);
};
return (
<div>
<h1>로그인</h1>
<div>
<AuthForm mode="login" onSubmit={handleLogin} />
</div>
<div>
<p>
계정이 없으신가요?{" "}
<Link to="/signup" >
회원가입
</Link>
</p>
</div>
</div>
);
};
export default Login;
//SignUp.jsx
import AuthForm from "../components/AuthForm";
import { Link, useNavigate } from "react-router-dom";
import useAuthStore from "../stores/authStore"; //zustand store 가져오기
const Signup = () => {
const signup = useAuthStore((state) => state.signup); //zustand 코드 적용 👏
const navigate = useNavigate();
const handleSignup = async (formData) => {
await signup(formData);
navigate("/login");
};
return (
<div>
<h1>회원가입</h1>
<AuthForm mode="signup" onSubmit={handleSignup} />
<div>
<p>
이미 계정이 있으신가요?{" "}
<Link to="/login">
로그인
</Link>
</p>
</div>
</div>
);
};
export default Signup;
이렇게 하면 회원가입도 데이터도 잘 들어가고, localStorage
코드를 작성하지 않았음에도 persist
미들웨어 때문에 저장되는 걸 확인할 수 있다!
zustand 문법이 낯설어서 전체적인 코드 길이에 비해 오래걸리긴 했는데 확실히 한번 적용해보고 나니까 왜 쌤들이나 같이 수업 듣는 분들이 코드가 훨씬 짧아졌다고 했는지 이해되었다!
앞으로도 여러번 써보면서 다양한 곳에서 휘뚤마뚤 쓸 수 있도록 연습해야지!🔥