준영이의 성장일기(FrontEnd)
UMC TUK 7주차 워크북 미션 본문
현재 교내에 있는 UMC TUK 6기 활동을 하고있고 WEB 파트의 스터디장으로서 어느 덧 7주차 스터디까지 도착했다. UMC 활동을 요약하자면 10주차 까지 워크북 작성을 무사히 완료한 챌린저들은 방학 때 8주간의 팀 프로젝트를 진행하고 데모데이를 통해 팀 프로젝트의 결과물을 발표한다. 어느새 7주차 활동이 된 지금 블로그를 조금 늦게 작성한 감이 있지만 이번 7주차 미션을 진행하며 문제를 해결한 과정에 대해서 정리하고자 한다. 이 전 미션들도(1주차~6주차) 해결 과정과 개념에 대해서 정리할 예정이다. 팀 프로젝트 전 까지 정리해보자!!
7주차 미션 중 가장 어려웠던 백엔드 서버와 통신하여 회원가입/로그인을 구현한 부분에 대해서 정리하고자 한다. 백엔드 서버 같은 경우에서는 UMC 리드님이 제공해주신 가상 백엔드 서버를 이용하였다.
<정리>
1. formData를 전송하는 함수 로직 구현
✅ 가상 백엔드 서버의 API 명세서에 맞추어서 엔드포인트를 작성하고 name~passwordCheck 데이터를 post 메소드를 통해 전송한다. formData의 형태는 키-값 형태의 객체로 구성되어 있다.
✅ 각 데이터별로 useState를 사용하는 것보다 객체로 하나의 데이터로 만든다면 코드의 가독성 용이
const [formData, setFormData] = useState({
name: "",
id: "",
email: "",
age: "",
password: "",
confirmPassword: "",
});
const handleSubmit = async (e) => {
e.preventDefault();
if (
Object.values(errors).every((error) => error === "") &&
Object.values(formData).every((value) => value !== "")
) {
try {
const response = await axios.post("http://localhost:8080/auth/signup", {
name: formData.name,
email: formData.email,
age: formData.age,
username: formData.id,
password: formData.password,
passwordCheck: formData.confirmPassword,
});
console.log("Form data is valid and was submitted:", response.data);
alert("회원가입이 성공적으로 완료되었습니다.");
navigate("/login");
} catch (error) {
console.error("Error during signup:", error);
if (error.response && error.response.data) {
alert(error.response.data.message);
} else {
alert("회원가입 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
} else {
alert("유효성 검사를 모두 통과해야 합니다.");
}
};
2. 데이터 통신이 문제없이 이루어졌는지 확인
✅ api명세서 대로 통신의 문제가 없을 때 token 그리고 username이 console에 찍히는지 확인
✅ 회원가입이 이상 없다면 자동으로 로그인 페이지로 라우팅 시작

3. 로그인 페이지에서 회원가입때 입력한 정보대로 로그인 시작

회원가입 때와 마찬가지로 가상 백엔드 서버의 API 명세서를 보고 post를 통해 전송할 데이터를 기입한다.
const handleSubmit = async (e) => {
e.preventDefault();
if (
Object.values(errors).every((error) => error === "") &&
Object.values(formData).every((value) => value !== "")
) {
try {
const response = await axios.post("http://localhost:8080/auth/login", {
username: formData.id,
password: formData.password,
});
console.log("login was submitted:", response.data);
navigate("/");
} catch (error) {
console.error("Error during login:", error);
if (error.response && error.response.data) {
alert(error.response.data.message);
} else {
alert("로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
console.log("Form data is valid and was submitted:", formData);
} else {
alert("유효성 검사를 모두 통과해야 합니다.");
}
};
✅로그인이 성공적으로 이루어지면 메인페이지로 라우팅
4. 로그인 토큰 정보를 로컬 스토리지에 저장하기
✅ {token}을 구조분해할당으로 가져오고 로컬 스토리지에 저장
✅ 이제 저장된 token의 키를 이용하여 token에 해당하는 user의 정보를 가져올 수 있다.
✅ 로그아웃 하게 되면 키에 해당하는 토큰을 제거한다.(Header.jsx에 코드 있음)
const handleLogout = () => {
localStorage.removeItem("token");
onLogout();
navigate("/login");
};
const handleSubmit = async (e) => {
e.preventDefault();
if (
Object.values(errors).every((error) => error === "") &&
Object.values(formData).every((value) => value !== "")
) {
try {
const response = await axios.post("http://localhost:8080/auth/login", {
username: formData.id,
password: formData.password,
});
const { token } = response.data;
localStorage.setItem("token", token);
console.log("login was submitted:", response.data);
navigate("/");
} catch (error) {
console.error("Error during login:", error);
if (error.response && error.response.data) {
alert(error.response.data.message);
} else {
alert("로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
}
}
console.log("Form data is valid and was submitted:", formData);
} else {
alert("유효성 검사를 모두 통과해야 합니다.");
}
};
5. 로그인에 따라서 Header,Banner에 조건부 랜더링을 적용한다.(ex. 로그인 상태일 때 로그아웃이 보이게, 배너에는 정준영님 환영합니다 랜더링 되도록)
<HeaderWithRouting.jsx>
✅ useEffect를 통해 랜더링 될 때 token에 해당하는 user의 정보를 가져오고 Header와 Banner에 props로 정보를 넘긴다.
✅ 원래는 useEffect를 Header.jsx, Banner.jsx에 각각 랜더링 코드를 구현하였는데 이렇게 되면 로그인 상태에 따라 즉각적으로 컴포넌트가 업데이트가 ❌ 그래서 같은 상태를 공유하도록 props로 Header.jsx, Banner.jsx에 user 정보를 props로 보내준다.
✅ Banner.jsx에서는 user를 props로 받고 user에 있는 user.name을 배너에 랜더링 한다.
import { useLocation, matchPath } from "react-router-dom";
import Header from "./Header";
import Banner from "./Banner";
import { useEffect, useState } from "react";
import axios from "axios";
export default function HeaderWithRouting() {
const location = useLocation();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUserData = async () => {
const token = localStorage.getItem("token");
if (token) {
try {
const response = await axios.get("http://localhost:8080/auth/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
setUser(response.data);
} catch (error) {
console.error("Error fetching user data:", error);
setUser(null);
}
}
setLoading(false);
};
fetchUserData();
}, []);
const handleLogout = () => {
setUser(null);
};
// 영화 상세 페이지에서는 헤더와 배너를 렌더링하지 않음
if (location.pathname.startsWith("/movie/")) {
return null;
}
const routes = [
"/",
"/login",
"/popular",
"/now-playing",
"/top-rated",
"/upcoming",
"/signup",
];
const match = routes.some((route) =>
matchPath({ path: route, end: route === "/" }, location.pathname)
);
// 현재 경로가 위 정의된 라우트 중 하나와 일치하지 않으면 NotFound로 간주
if (!match) {
return null; // NotFound 페이지에서는 헤더를 렌더링하지 않음
}
// 그 외의 경우(정의된 경로에서 동작하는 페이지) 헤더 렌더링
return (
<>
<Header user={user} loading={loading} onLogout={handleLogout} />
{location.pathname === "/" && <Banner user={user} loading={loading} />}
</>
);
}
<Header.jsx, Banner.jsx>
✅ user에 따라서 조건부 랜더링 적용
import React from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import styled from "styled-components";
const HeaderBox = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 70px;
min-height: 70px;
border-bottom: 1px solid black;
background-color: #101021;
color: white;
`;
const MainLogo = styled(Link)`
display: flex;
align-items: center;
font-size: medium;
font-weight: bold;
padding: 10px;
height: 100%;
box-sizing: border-box;
color: white;
&:hover {
text-decoration: none;
color: white;
font-size: larger;
transition: font-size 0.2s ease;
}
`;
const MovieCategory = styled.div`
display: flex;
justify-content: space-evenly;
height: 100%;
padding: 10px;
box-sizing: border-box;
`;
const CategoryList = styled.div`
display: flex;
align-items: center;
height: 100%;
margin-right: 10px;
`;
const StyledLink = styled(Link)`
margin-right: 20px;
color: ${(prop) => (prop.param === "/signup" ? "yellow" : "white")};
font-weight: ${(prop) => (prop.param === "/signup" ? "bold" : "")};
text-decoration: none;
&:last-child {
margin-right: 0;
}
&:hover {
text-decoration: none;
color: white;
font-size: larger;
font-weight: bold;
transition: font-size 0.2s ease;
}
`;
export default function Header({ user, loading, onLogout }) {
const location = useLocation();
const navigate = useNavigate();
const handleLogout = () => {
localStorage.removeItem("token");
onLogout();
navigate("/login");
};
return (
<HeaderBox>
<MainLogo to="/">UMC MOVIE</MainLogo>
<MovieCategory>
<CategoryList>
{user ? (
<>
<StyledLink onClick={handleLogout}>로그아웃</StyledLink>
<StyledLink to="/popular">Popular</StyledLink>
<StyledLink to="/now-playing">Now Playing</StyledLink>
<StyledLink to="/top-rated">Top Rated</StyledLink>
<StyledLink to="/upcoming">Upcoming</StyledLink>
</>
) : (
<>
<StyledLink to="/login">로그인</StyledLink>
<StyledLink to="/signup" param={location.pathname}>
회원가입
</StyledLink>
<StyledLink to="/popular">Popular</StyledLink>
<StyledLink to="/now-playing">Now Playing</StyledLink>
<StyledLink to="/top-rated">Top Rated</StyledLink>
<StyledLink to="/upcoming">Upcoming</StyledLink>
</>
)}
</CategoryList>
</MovieCategory>
</HeaderBox>
);
}
import React from "react";
import styled from "styled-components";
const BannerBox = styled.div`
width: 100%;
background-color: black;
font-size: larger;
`;
const BannerTitle = styled.div`
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
width: 100%;
height: 300px;
`;
export default function Banner({ user, loading }) {
return (
<BannerBox>
<BannerTitle>
{loading
? "로딩 중..."
: user
? `${user.name}님 환영합니다!`
: "환영합니다"}
</BannerTitle>
</BannerBox>
);
}
<깃허브>
'프론트엔드 > React.js' 카테고리의 다른 글
UMC 10주차 미션 및 스터디 마무리 (0) | 2024.06.16 |
---|---|
Open Api를 활용한 여행 일정 구상하는 프로젝트(3) (2) | 2024.06.06 |
UMC 영화 페이지에 반응형 디자인 적용하기 (0) | 2024.06.02 |
Open Api를 활용한 여행 일정 구상하는 프로젝트(2) (0) | 2024.05.30 |
Open Api를 활용한 여행 일정 구상하는 프로젝트(1) (2) | 2024.05.29 |