Skip to content
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

글 작성/수정 폼 리팩토링해서 폼 훅 분리하기 #838

Merged
merged 10 commits into from
Nov 14, 2023
Merged

Conversation

chsua
Copy link
Collaborator

@chsua chsua commented Nov 8, 2023

🔥 연관 이슈

close: #837

📝 작업 요약

  • 글 작성/수정 폼 리팩토링
  • 마감시간 오류 수정
  • 리다이렉트 오류 수정

⏰ 소요 시간

1일

🔎 작업 상세 설명

  • 글 작성/수정 폼 리팩토링
    • 게시글 정보를 form-data로 바꾸는 과정 유틸함수로 분리
    • 마감시간 관리하는 로직을 useDeadline 훅으로 분리
  • 마감시간 오류 수정
    • 모달 초기화를 누르면 게시글이 제출되는 로직 수정
    • 모달 확인 누르면 유효시간 확인 안하고 바로 설정되는 오류 수정
  • 리다이렉트 오류 수정
    • 마감시간 여부를 검증하지 않아 진입되는 오류 수정
    • 참여자가 있는지 검증하지 않아 진입되는 오류 수정

🌟 논의 사항

글 작성/수정 폼 리팩토링을 하면 많이 비워질 줄 알았는데, 생각보다 비워지지 않더라구요.

useText, useContentImage 등 훅을 사용하는 경우 이걸 다시 모아 리턴하는 것이 유의미할지 잘 모르겠어서 그대로 두었습니다. 분리를 하는 것도 의미가 있겠지만 파악하기 위한 뎁스가 깊어져 오히려 가독성을 떨어트릴 수 있다고 생각했습니다.

결과적으로 폼 안에는

  • 훅(useText, useContentImage 등)
  • 리다리엑트 검증 로직
  • 이벤트 핸들러
  • 모달용 button

위 로직들만 존재하고 있습니다. 혹시 더 뺄 수 있거나 리팩토링 가능해보인다면 말씀해주세요!

ps. 함수명 중 ---Formdata가 있는데, 이는 문법상 이상하지만 FormData라고 하면 오히려 폼데이터 형식처럼 와닿지 않을 것 같아서 붙여서 작성했습니다. 이에 대해 좋은 의견이 있다면 말씀해주세요!(변수명 너무 어렵네요😢)

- 수정 페이지 url로 접근 시 마감시간이 지난 글을 수정할 수 있는 오류 수정
- 타임피커 모달을 확인을 누르면 시간 검증없이 시간이 설정되는 오류 수정
- 폼데이터를 만드는 정보객체 재활용
- 마감시간 정보가 stringData 타입으로 수정되며 0일 0시간 0분을 검열하기 어려워짐 -> 코드 삭제
- 모달에 넣는 buttonInfo가 ButtonHTMLAttributes 상속하게 만들어 type을 지정할 수 있도록 함
- 모달과 폼에서 모두 사용되어 타입을 전역 타입폴더로 분리
Copy link

github-actions bot commented Nov 8, 2023

⚡️ Lighthouse report!

Category Score
🟠 Performance 50
🟠 Accessibilty 89
🟢 SEO 100
🟠 PWA 89
Category Score
🟢 First Contentful Paint 0.7 s
🔴 Largest Contentful Paint 5.7 s
🔴 Total Blocking Time 1,990 ms
🟢 Cumulative Layout Shift 0.046
🟠 Speed Index 3.6 s

Copy link
Collaborator

@Gilpop8663 Gilpop8663 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

복잡했던 PostForm이 훨씬 읽기 좋아졌다고 생각해요 👍

그리고 저도 수아의 의견처럼 useContent와 useText를 모아서 반환하는 함수는 과하다고 생각이 들어요

const errorMessage = checkValidationPost(writingPostInfo);
if (errorMessage) return addMessage(errorMessage);

const formData = convertToFormdata(writingPostInfo);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const formData = convertToFormData(writingPostInfo);

카멜케이스로 변경 가능할까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 일부로 Formdata로 하신 부분이군요!

저는 FormData와 Formdata의 미묘한 차이를 잘 모르겠어요. 변수명 짓는것은 정말 어렵네요

convertToPostFormData 은 어떠세요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 함수가 글 작성 폼에 한정되어 있다고 생각해서 우스의 의견에 동의합니다!

};

const secondaryButton = {
const secondaryButton: ButtonInfo = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모달의 Button 관련 타입이 types로 나오게 된 이유가 궁금했는데 사용하는 곳에서 타입을 선언해줘야 했군요!

ButtonInfo도 좋지만 Modal 관련 버튼 타입이니까 ModalButton 은 어떠세요?


import { deleteOverlappingNewLine } from '@utils/deleteOverlappingNewLine';

export const convertToFormdata = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유틸 함수로 분리되니 코드 가독성이 올라갔어요 😀

Comment on lines +93 to +106
if (deadline && Number(new Date(deadline)) < Date.now()) {
addMessage('마감완료된 게시물은 수정할 수 없습니다.');
return <Navigate to={PATH.HOME} />;
}

if (postId && writer && !checkWriter(writer.id)) {
addMessage('사용자가 작성한 글만 수정할 수 있습니다.');
return <Navigate to={PATH.HOME} />;
}

if (serverVoteInfo && serverVoteInfo.allPeopleCount !== 0) {
addMessage('투표한 사용자가 있어 글 수정이 불가합니다.');
return <Navigate to={PATH.HOME} />;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 처리로 서비스 완성도가 올라갔네요 👍👍👍

@Gilpop8663
Copy link
Collaborator

fe-리뷰완

Copy link
Member

@inyeong-kang inyeong-kang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우 수아~~ 리팩터링 달인이시네요!
가장 리팩터링이 시급한 컴포넌트라고 생각했는데 나서주셔서 감사합니다👍

light

추가로 수정했으면 좋을 부분 코멘트로 남겼습니다~~

Comment on lines +93 to +106
if (deadline && Number(new Date(deadline)) < Date.now()) {
addMessage('마감완료된 게시물은 수정할 수 없습니다.');
return <Navigate to={PATH.HOME} />;
}

if (postId && writer && !checkWriter(writer.id)) {
addMessage('사용자가 작성한 글만 수정할 수 있습니다.');
return <Navigate to={PATH.HOME} />;
}

if (serverVoteInfo && serverVoteInfo.allPeopleCount !== 0) {
addMessage('투표한 사용자가 있어 글 수정이 불가합니다.');
return <Navigate to={PATH.HOME} />;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 이 3가지 validation도 함수로 분리해보는거 어떨까요?
3가지 유효성 검사(수정 가능한지 아닌지 검사)를 통과하면 isValid 가 true가 되고,

if(!isPostFormEditValid)  {
    addMessage('유효하지 않은 이유');
    return <Navigate to={PATH.HOME} />;
  }

요런 식으로 리팩터링할 여지가 보이네요!!

resetDeadline,
getFinalDeadline,
getLimitDeadline,
} = useDeadline(createTime, deadline);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

궁금한 점! 혹시 파일명과 함수명을 다르게 하신 이유가 있나요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 중간에 수정했는데 신경쓰지 못했습니다 수정하겠습니다!

if (serverVoteInfo && serverVoteInfo.allPeopleCount !== 0) {
addMessage('투표한 사용자가 있어 글 수정이 불가합니다.');
return <Navigate to={PATH.HOME} />;
}

// 마감시간 관련 핸들러
const handleDeadlineButtonClick = (option: DeadlineOptionInfo) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수 로직도 useDeadline hook에 포함시키는건 어떠신가요?
hook 하나 분리한 김에 관심사 같은 것은 최대한 하나로 빼내는 것도 좋을거 같아서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 고민했는데, 핸들러 로직은 버튼이나 모달 등 다른 정보와 관련있는데 훅 안으로 옮기는 것이 관심사가 섞이는 것이라고 생각했습니다. 그래서 마감시간을 수정하는 로직만 훅으로 두고 핸들러에서 훅에서 return 함수/변수를 조작-이용하는 것이 좋다고 생각했습니다!

setSelectTimeOption(option.name);
setUserSelectTime(targetTime);

changeDeadlineOption(option);
};

const handleResetButton = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수도 useDeadline hook에 포함시키는게 좋을거 같아요!
(handleResetDeadline 이라는 함수명으로)

const errorMessage = checkValidationPost(writingPostInfo);
if (errorMessage) return addMessage(errorMessage);

const formData = convertToFormdata(writingPostInfo);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 함수가 글 작성 폼에 한정되어 있다고 생각해서 우스의 의견에 동의합니다!

hour: userSelectTime.hour,
day: userSelectTime.day,
minute: userSelectTime.minute,
hour: userSelectedDHMTime.hour,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍구체적인 변수명으로 의미가 더 명확해졌네요!!


import { Option } from '@components/common/MultiSelect/types';

interface OptionWithIsServerId extends WritingVoteOptionType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 OptionWithIsServerId 에서 Is 는 어떤 의미인지 궁금합니다!

타입 확장 좋아요!!!👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 isServerId는 클라이언트(프론트엔드)에서만 가지는 필드입니다
서버에서 준 선택지와 사용자가 입력한 선택지를 구분하기 위함으로 해당 필드가 포함된 옵션 타입이라고 명시하기 위함입니다.

@@ -217,7 +230,7 @@ export const WithTimePicker = () => {
>
<S.Body>
<S.Description>최대 {MAX_DEADLINE}일을 넘을 수 없습니다.</S.Description>
<TimePickerOptionList time={time} setTime={setTime} />
<TimePickerOptionList time={time} setTime={changeDeadlinePicker} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 구체적인 상태가 되었네요!👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 스토리인지 이름도 같이 구체화하면 좋을거 같아서, 혹시 WithTimePicker 라는 이름 대신 WithDeadlineTimePicker 로 바꿔주실 수 있나요?!

return (
<>
<TimePickerOptionList time={time} setTime={setTime} />
<TimePickerOptionList time={time} setTime={changeDeadlinePicker} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수명 바꾼 김에 Default 라는 스토리 이름 대신 Deadline 으로 바꿔주실 수 있나요?

@inyeong-kang
Copy link
Member

fe-리뷰완

@chsua chsua merged commit fdca208 into dev Nov 14, 2023
1 check failed
@woo-chang woo-chang deleted the feat/#837 branch December 6, 2023 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

글 작성/수정 폼 리팩토링해서 폼 훅 분리하기
3 participants