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

[최영선] sprint10 #311

Conversation

choi-youngsun
Copy link
Collaborator

@choi-youngsun choi-youngsun commented Aug 23, 2024

요구사항

기본

  • 게시글 등록 페이지 주소는 “/addboard” 입니다.

  • 게시판 이미지는 최대 한개 업로드가 가능합니다.

  • 각 input의 placeholder 값을 정확히 입력해주세요.

  • 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다

  • 게시글 상세 페이지 주소는 “/board/{id}” 입니다.

  • 댓글 input 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.

  • 자유게시판 페이지에서 게시글을 누르면 게시물 상세 페이지로 이동합니다

  • 게시글 상세 페이지 주소는 “/board/{id}” 입니다.

  • 댓글 input 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.

심화

  • 회원가입, 로그인 api를 사용하여 받은 accessToken을 사용하여 게시물 등록을 합니다.
  • ‘등록’ 버튼을 누르면 게시물 상세 페이지로 이동합니다

주요 변경사항

  • 각 페이지 디자인 완성
  • 상세페이지 기능 완성

스크린샷

스크린샷 2024-08-24 183237
스크린샷 2024-08-24 183251

멘토에게

  • 아직 유저 인증 기능을 다 안 배워서, 학습 후에 이어서 구현하겠습니다!

@choi-youngsun choi-youngsun added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🫠 죄송합니다.. labels Aug 23, 2024
@choi-youngsun choi-youngsun changed the base branch from React-최영선 to Next-최영선 August 23, 2024 12:26
Copy link
Collaborator

@arthurkimdev arthurkimdev left a comment

Choose a reason for hiding this comment

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

마지막 과제 제출하시느라 고생 많으셨습니다 🙏

Comment on lines +249 to +262
<form onSubmit={handleSubmit}>
<StyledTopSection>
<StyledTitle>댓글달기</StyledTitle>
<StyledButton disabled={!checkAllInputsFilled()}>등록</StyledButton>
</StyledTopSection>
<label htmlFor="comment" />
<StyledTextArea
id="comment"
name="comment"
value={commentValue.comment}
placeholder="댓글을 입력해주세요"
onChange={handleInputChange}
/>
</form>
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 폼 같은 경우, 별도 아래처럼 컴포넌트로 분리하면 board[id] 페이지 내 jsx가 줄면서 가독성이 좋아지고, 다른 코드에 더 집중할 수 있습니다. 나눌 수 있는 컴포넌트가 있으면 나누는 것도 좋아요. 무조건 나눠서 방해가 되지 않게 해야되지만, 보통 이런 폼은 별도로 나눕니다.

function CommentForm({ onSubmit }: { onSubmit: (comment: string) => void }) {
  const [comment, setComment] = useState("");

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(comment);
    setComment("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <StyledTopSection>
        <StyledTitle>댓글달기</StyledTitle>
        <StyledButton disabled={!comment}>등록</StyledButton>
      </StyledTopSection>
      <label htmlFor="comment">댓글 입력</label>
      <StyledTextArea
        id="comment"
        name="comment"
        value={comment}
        placeholder="댓글을 입력해주세요"
        onChange={(e) => setComment(e.target.value)}
        aria-label="댓글 입력"
      />
    </form>
  );
}

const [Detail, setDetail] = useState<Article>();
const [commentValue, setCommentValue] = useState({ comment: "" });
const router = useRouter();
const { id } = router.query;
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기 타입 처리에 어려움이 있어서 결국 아래 컴포넌트에 props로 전달할 때 any가 사용되었는데요,

router.query 의 기본 타입 값은 string | string[] | undefined 입니다.

  1. string: 단일 값일 때 (예: /article/123)
  2. string[]: 여러 값이 있을 때 (예: /article/123?id=456)
  3. undefined: 페이지가 처음 로드될 때 또는 id 파라미터가 없을 때

결국 아래처럼 정해볼 수 있어요.

const { id } = router.query as { id?: string | string[] };

Copy link
Collaborator

Choose a reason for hiding this comment

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

뿐만 아니라, 실제 사용할 때는 아래와 같은 이유로 타입 가드를 만들어 타입을 좁힌 후 사용하기도 합니다.

// 타입 가드 없이 사용할 경우
function fetchArticle(id: string | string[] | undefined) {
  // 이 함수 내에서 id를 어떻게 안전하게 사용할 수 있을까요?
  // id가 undefined일 수도 있고, 배열일 수도 있어서 바로 사용하기 어렵습니다.
}



// 타입 가드를 사용한 경우
if (typeof id === 'string') {
  fetchArticle(id); // 여기서 id는 확실히 string 타입입니다.
} else if (Array.isArray(id)) {
  fetchArticle(id[0]); // 여기서 id는 확실히 string[] 타입이며, 첫 번째 요소를 안전하게 사용할 수 있습니다.
} else {
  console.log('id가 없습니다.'); // id가 undefined인 경우를 명확히 처리할 수 있습니다.
}

Comment on lines +112 to +119
<StyledWriterImg
src={
comment.writer.image
? comment.writer.image
: "/image/profile_img_none.png"
}
alt="프로필 이미지"
/>
Copy link
Collaborator

Choose a reason for hiding this comment

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

웹 접근성 관점에서 alt 속성 값에 이런 경우 사용자닉네임도 넣어서 값을 만들면 대체 텍스트를 보다 더 구체적으로 만들 수 있어요.

          alt={`${comment.writer.nickname}의 프로필 이미지`}

Comment on lines +70 to +86
const [comments, setComments] = useState<Comment[]>([]);

async function getArticleComments() {
const query = {
limit: 10,
};
const res = await axios.get(
`/articles/${id}/comments?limit=${query.limit}`
);
const nextComments = res.data.list;
setComments(nextComments);
}

useEffect(() => {
getArticleComments();
}, [id]);

Copy link
Collaborator

@arthurkimdev arthurkimdev Aug 27, 2024

Choose a reason for hiding this comment

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

이 부분은 아래처럼 useArticleComments 커스텀 훅 같은걸 만들어서 재사용 가능한 훅을 만들어 볼 수 있겠어요. 😸

function useArticleComments(id: string, limit: number) {
  const [comments, setComments] = useState<Comment[]>([]);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    async function fetchComments() {
      setLoading(true);
      try {
        const res = await axios.get(`/articles/${id}/comments?limit=${limit}`);
        setComments(res.data.list);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown error occurred'));
      } finally {
        setLoading(false);
      }
    }

    if (id) {
      fetchComments();
    }
  }, [id, limit]);

  return { comments, error, loading };
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

그 이후에는 아래처럼 사용해보면 되겠죠?

function ArticleCommentList({ id, limit = 10 }: ArticleCommentListProps) {
  const { comments, error, loading } = useArticleComments(id, limit);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error.message} />;
  if (comments.length === 0) return <EmptyCommentState />;

  ...

Comment on lines +108 to +119
{comments.map((comment) => (
<StyledListItem key={comment.id}>
<StyledContent>{comment.content}</StyledContent>
<StyledBottomSection>
<StyledWriterImg
src={
comment.writer.image
? comment.writer.image
: "/image/profile_img_none.png"
}
alt="프로필 이미지"
/>
Copy link
Collaborator

Choose a reason for hiding this comment

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

보통 이렇게 map을 사용할 수 있는 부분은 별도의 컴포넌트로 분리하는걸 추천드려요. UI가 분리되고, 가독성이 좋아지거든요.
다른 곳에 재사용도 가능해지구요.

function CommentItem({ comment }: { comment: Comment }) {
  return (
    <StyledListItem as="article">
      <StyledContent>{comment.content}</StyledContent>
      <StyledBottomSection>
        <StyledWriterImg
          src={comment.writer.image || "/image/profile_img_none.png"}
          alt={`${comment.writer.nickname}의 프로필 이미지`}
        />
        <StyledInfo>
          <StyledNickname>{comment.writer.nickname}</StyledNickname>
          <StyledTime>
            <FormatRelativeTime time={comment.updatedAt} />
          </StyledTime>
        </StyledInfo>
      </StyledBottomSection>
    </StyledListItem>
  );
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

최종적으로 아래처럼 바꿀 수 있어요.

return (
    <CommentSection>
      <CommentList>
        {comments.map((comment) => (
          <CommentItem key={comment.id} comment={comment} />
        ))}
      </CommentList>
    </CommentSection>
  );

@arthurkimdev arthurkimdev merged commit 28e1902 into codeit-bootcamp-frontend:Next-최영선 Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🫠 죄송합니다..
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants