-
Notifications
You must be signed in to change notification settings - Fork 46
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
[오병훈] Sprint10 #309
The head ref may contain hidden characters: "Next-\uC624\uBCD1\uD6C8-sprint10"
[오병훈] Sprint10 #309
Conversation
import SortIcon from '@public/svgs/ic_sort.svg'; | ||
import styles from '@styles/DropdownMenu.module.css'; | ||
import { ArticleOrderBy } from '@components/types/articleTypes'; |
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.
리뷰 반영 굳 입니다! 👍
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.
지난번 리뷰 반영해주시느라 고생 많으셨습니다! 수고하셨습니다~ 👍
export function AuthProvider({ children }: AuthProviderProps) { | ||
async function login(credentials: { email: string; password: string }) { | ||
try { | ||
const response = await axiosInstance.post('/auth/signIn', credentials); | ||
const { accessToken } = response.data; | ||
|
||
localStorage.setItem('accessToken', accessToken); | ||
console.log('로그인 성공'); | ||
} catch (error) { | ||
console.error('로그인 실패:', error); | ||
throw new Error('로그인에 실패했습니다.'); | ||
} | ||
} | ||
|
||
return ( | ||
<AuthContext.Provider | ||
value={{ | ||
login, | ||
}} | ||
> | ||
{children} | ||
</AuthContext.Provider> | ||
); | ||
} | ||
|
||
export function useAuth() { | ||
const context = useContext(AuthContext); | ||
|
||
if (!context) { | ||
throw new Error('반드시 AuthProvider 안에서 사용해야 합니다.'); | ||
} | ||
|
||
return context; | ||
} |
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.
context API를 사용할 때는, value를 useMemo로 감싸서 불필요한 리렌더링 방지 처리를 꼭 해줘야해요.
라이브러리(redux, zustand 등) 을 사용 안하게 될 땐, 리랜더링 최적화를 저희가 직접 챙겨야하거든요.
const value = useMemo(() => ({
login,
}), [login]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
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.
useMemo뿐 아니라, useCallback도 추가할 수 있습니다.
const login = useCallback(async (credentials: { email: string; password: string }) => {
try {
const response = await axiosInstance.post('/auth/signIn', credentials);
const { accessToken } = response.data;
localStorage.setItem('accessToken', accessToken);
console.log('로그인 성공');
} catch (error) {
console.error('로그인 실패:', error);
throw new Error('로그인에 실패했습니다.');
}
}, []);
const handleInputChange = ( | ||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, | ||
) => handleChange(e.target.name, e.target.value); | ||
|
||
useEffect(() => { | ||
const { title, content } = values; | ||
if (title && content) { | ||
setIsFormValid(true); | ||
} else { | ||
setIsFormValid(false); | ||
} | ||
}, [values]); | ||
|
||
const handleSubmit = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
|
||
const { title, content } = values; | ||
const accessToken = localStorage.getItem('accessToken'); | ||
|
||
if (!accessToken) { | ||
alert('로그인이 필요합니다.'); | ||
return; | ||
} | ||
|
||
const data = { | ||
title, | ||
content, | ||
image: | ||
'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Sprint_Mission/user/3/1721991853452/5389615.png', | ||
}; | ||
|
||
try { | ||
const response = await axiosInstance.post('articles', data, { | ||
headers: { | ||
Authorization: `Bearer ${accessToken}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
console.log('게시글 등록 성공:', response.data); | ||
alert('게시글이 등록되었습니다.'); |
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.
해당 파일 전반적으로 아래 처럼 커스텀 훅을 통해 로직이 분리되어 개선이 되면 코드 가독성이 향상되고, 유지보수에 도움이 될 것 같아요. 아래 구조로 한번 고민해보세요.
export default function AddBoardPage({
initialValues = INITIAL_VALUES,
initialPreview,
}: BoardCreateFormProps) {
const { values, isValid, handleChange } = useFormState(initialValues);
const { submitArticle } = useArticleSubmit();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await submitArticle({
title: values.title,
content: values.content,
image: '...' // 이미지 URL
});
alert('게시글이 등록되었습니다.');
} catch (error) {
alert('게시글 등록에 실패했습니다.');
console.error(error);
}
};
return (
<div className={styles.container}>
<form onSubmit={handleSubmit}>
<FormHeader isValid={isValid} />
<div className={styles['container-body']}>
<FormField
label="*제목"
name="title"
value={values.title}
onChange={handleInputChange}
placeholder="제목을 입력해주세요."
/>
<FormField
as="textarea"
label="*내용"
name="content"
value={values.content}
onChange={handleInputChange}
placeholder="내용을 입력해주세요."
/>
<FileInput
label="이미지"
name="imgFile"
value={values.imgFile}
initialPreview={initialPreview}
onChange={handleChange}
/>
</div>
</form>
</div>
);
}
{comments.map((comment, index) => ( | ||
<div className={styles['comment-container']} key={index}> | ||
<div className={styles['comment-header']}> | ||
<div className={styles['comment-content']}> | ||
{comment.content} | ||
</div> | ||
<SortIcon /> | ||
</div> | ||
<div className={styles['comment-info']}> | ||
<div className={styles['comment-info-content']}> | ||
{comment.writer.image ? ( | ||
<img | ||
className={styles['profile-icon']} | ||
src={comment.writer.image} | ||
/> | ||
) : ( | ||
<Profile className={styles['profile-icon']} /> | ||
)} | ||
<div className={styles['comment-user-info']}> | ||
<div className={styles['comment-nickname']}> | ||
{comment.writer.nickname} | ||
</div> | ||
<div className={styles['comment-updatedAt']}> | ||
{formatDate(comment.updatedAt)} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
))} | ||
</div> |
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.
이렇게 리스트를 map 사용해서 UI 표시할 수 있는 부분은 <CommentList comments={comments} />
이렇게 별도로 분리해서 가독성을 향상 시키면 좋겠습니다. 😸
const [email, setEmail] = useState(''); | ||
const [password, setPassword] = useState(''); | ||
const [isEmailValid, setIsEmailValid] = useState(true); | ||
const [isPasswordValid, setIsPasswordValid] = useState(true); | ||
const [isFormValid, setIsFormValid] = useState(false); | ||
const [showPassword, setShowPassword] = useState(false); | ||
|
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.
이렇게 객체로도 관리할 수 있겠어요! 🧐
const [formData, setFormData] = useState<LoginFormState>({
email: '',
password: ''
});
const [validation, setValidation] = useState<ValidationState>({
email: true,
password: true,
form: false
});
useEffect(() => { | ||
const emailValid = validateEmail(email); | ||
const passwordValid = password.length >= 8; | ||
setIsEmailValid(emailValid || email === ''); | ||
setIsPasswordValid(passwordValid || password === ''); | ||
setIsFormValid(emailValid && passwordValid); | ||
}, [email, password]); |
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.
아래처럼 검증로직을 보다 더 단순화할 수 있습니다.
const validateEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
useEffect(() => {
const isEmailValid = validateEmail(formData.email);
const isPasswordValid = formData.password.length >= 8;
setValidation({
email: isEmailValid || !formData.email,
password: isPasswordValid || !formData.password,
form: isEmailValid && isPasswordValid
});
}, [formData]);
요구사항
기본
상품 등록 페이지
상품 상세 페이지
심화
주요 변경사항
스크린샷
멘토에게