-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[김영주] Sprint11 #316
The head ref may contain hidden characters: "React-\uAE40\uC601\uC8FC-sprint11"
[김영주] Sprint11 #316
Conversation
</div> | ||
<ul className="flex flex-row gap-3"> | ||
<li> | ||
<Link to="https://www.facebook.com/"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
sns 별 고정 링크와 같은 경우에는 상수로 만들어서 관리하는 것도 좋을 것 같아요!
const SNS_LINK = Object.freeze({
FACEBOOK: "https://www.facebook.com/",
TWITTER: "https://x.com/",
YOUTUBE: "https://www.youtube.com/",
INSTAGRAM: "https://www.instagram.com/"
})
window.addEventListener("resize", handleResize); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
리사이즈 관련한 로직은 커스텀훅으로 따로 분리해도 활용도가 높을 것 같습니다!
ex) useResize 에 로직 분리
const { isMobile } = useResize();
return ( | ||
<section className="flex flex-col content-center gap-[40px] "> | ||
<ul className="w-full flex flex-col content-start flex-grow gap-6"> | ||
{commentsData.list.length === 0 ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
!comments.list.length
이 조건으로도 검사할 수 있을 것 같아요!
혹은 리턴부 위에서 해당 조건을 한 번더 변수화해서 사용해도 괜찮을 것 같아요.
const hasNoComments = !comments.list.length;
return (
<div>
{hasNoComents ? ... : ...}
</div>
)
/> | ||
<p className="text-gray-400"> | ||
아직 {category} | ||
{hasFinalConsonant(category) ? "이" : "가"} 없어요. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
디테일한 ux 처리 좋습니다👍
const LikeCount = ({ count, divClassName }: LikeCountProps) => { | ||
return ( | ||
<div | ||
className={`${divClassName} flex items-center text-xs font-medium gap-1 text-gray-500`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
divClassName으로 props를 전달 받는 것보다는 wrapperClassName 등의 이름이 유지보수하기 더 좋을 것 같아요!
// GET, 공통 apiCall 함수 | ||
export async function getFromApi<T>(url: string): Promise<T> { | ||
try { | ||
const response = await fetch(url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
fetch 시에 엔드포인트만 작성할 수 있도록 여기 BASE_URL을 설정해놓는 것도 좋을 것 같아요!
try {
const response = await fetch(`${BASE_URL}${path}`);
return { data, error, loading, postData }; | ||
} | ||
|
||
export default useApiPost; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POST 요청에 대한 로딩, 에러 및 데이터와 콜백 등 사용하시기 좋게 추상화를 잘 해놓으셨네요!
if (token) { | ||
fetchUserInfo(token); | ||
} | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p3;
로컬스토리지의 토큰을 통해 인증을 잘 구현하셨어요!
다만, 인증과 관련된 상태의 경우 일반적으로 전체 페이지에 걸쳐 전역적으로 사용되는 경우가 많아서,
요 user 관련한 로직을 UserProfile 내에 한정하여 처리하는 대신
Context를 하나 만들어, 모든 컴포넌트에서 로그인 상태나 유저 정보가 필요한 경우 useContext로 접근할 수 있도록
처리하면 어떨까요?
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem("accessToken");
if (!token) return;
fetchUserInfo(token);
}, []);
const handleLogout = () => {
localStorage.removeItem("accessToken");
setUser(null);
};
...
return (
<AuthContext.Provider value={{ user, loading, handleLogin, handleLogout }}>
{children}
</AuthContext.Provider>
);
};
...
<AuthProvider>
<Routes>
...
</Routes>
</AuthProvider>
...
const UserProfile = () => {
const { user } = useContext(AuthContext);
...
}
const [isLoggedOutVisible, setIsLoggedOutVisible] = useState<boolean>(false); | ||
|
||
useEffect(() => { | ||
const token = localStorage.getItem("accessToken"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
추후에 여유가 되신다면, accessToken의 만료기한을 검증하고 refreshToken 등으로 이를 갱신하는 로직을 추가해봐도 좋을 것 같아요!
jwt 토큰에는 일반적으로 만료기한이 설정되어 있습니다.
서버에서 전달 받은 jwt를 디코딩하면 만료 기한(Exp)을 확인하실 수 있습니다.
따라서, 로컬스토리지에 저장되어 있는 accessToken의 값을 디코딩하여,
만료 시간을 파악하고 토큰이 만료된 경우, 새로운 accessToken을 발급받는 등의 처리도 하실 수 있습니다
예를들어 Jwt의 유효 시간을 확인하는 방식은 아래와 같습니다.
// jwt 디코딩
const decodeToken = (token) => {
const payload = token.split('.')[1];
const decodedPayload = atob(payload);
return JSON.parse(decodedPayload);
}
// 토큰의 유효 시간 확인 함수
const isTokenExpired = (token) => {
const decoded = decodeToken(token);
const currentTime = Math.floor(Date.now() / 1000);
return decoded.exp < currentTime; // exp가 현재 시간보다 이전이면 토큰 만료
}
className="h-8 w-8 cursor-pointer" | ||
onClick={handleProfileClick} | ||
/> | ||
{isLoggedOutVisible && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4;
요건 따로 loggedOutVisible 상태를 관리하기보다는,
기존 user 정보의 상태를 이용하여 null인 경우에 '로그아웃' 텍스트가 보여질 수 있도록 처리하는 건 어떨까요?
{!user && ( <button> 로그아웃 </button>)}
영주님 고생 많으셨습니다! 이번에 코드를 보니 특히 api 호출하는 부분에서는 로직들을 재사용하기 위해 추상화를 시도하신 흔적이 잘 느껴졌습니다. 별개로 천천히 여유가 되신다면 next도 한 번 적용하셔서, 기존 순수 리액트 프로젝트로 할때와 어떤 장단점이 있는지 경험해보는 것도 좋을 것 같아요! |
요구사항
기본
심화
주요 변경사항
- 클릭 시 로컬 스토리지에 보관된 토큰 삭제
스크린샷
멘토에게