Skip to content

leezer94/pre-onboarding-8th-week4

Repository files navigation

📝 Comment 목록 CRUD 및 Pagination 구현

📄목차




📚 사용 라이브러리




🏃‍♂️ 실행방법


  • 의존성 package 설치
npm install
  • 브라우저 실행
npm run start
  • json-server 실행
npm run api

💡 구현 목표


API 서버와 통신하여 작동하는 댓글 프로젝트를 Redux를 통해 구현하기

  • 댓글 불러오기, 작성, 수정, 삭제가 동작하도록 기능 구현 ( CRUD )

  • 페이지네이션 구현




1. 댓글 프로젝트 CRUD


  • 추가 조건 ( 댓글 작성, 수정, 삭제 후 동작 )
    • 댓글 작성하고 난 뒤: 다른 페이지에 위치하고 있었더라도 1페이지로 이동, 입력 폼 초기화
    • 댓글 수정하고 난 뒤: 현재 보고있는 페이지 유지, 입력 폼 초기화
    • 삭제하고 난 뒤: 1페이지로 이동

Component

  • 댓글 Create과 Update의 경우, Redux를 통해 '수정모드(modifyMode)' 여부(T/F)를 전역적으로 관리 하여 'Form.js' Component를 공통으로 사용하도록 구현
function Form() {
  const dispatch = useDispatch();
  const { modifyMode, modifySelectInfo, currentPageNumber } = useSelector(
    (state) => state.comment
  );
  const { goToPage1 } = useComment();
 ...

  const inputReset = useCallback(() => {
    setProfileURL("");
    setAuthor("");
    setContent("");
    setCreateAt("");
  }, [setAuthor, setContent, setCreateAt, setProfileURL]);

  const onSubmitComment = (e) => {
  ...
    if (modifyMode) {
      dispatch(MODIFY_COMMENT({ id: modifySelectInfo.id, infoData }));
      dispatch(GET_COMMENTS_CURRENT_PAGE(currentPageNumber));
      dispatch(SET_MODIFY_MODE(modifySelectInfo.id));
      inputReset();
    } else {
      dispatch(ADD_COMMENT(infoData));
      inputReset();
      goToPage1(); // 댓글 작성 후 1페이지로 이동
    }
  };

  // 수정 버튼 눌렀을 때 입력 폼 value 변경
  useEffect(() => {
    setProfileURL(modifySelectInfo.profile_url);
    setAuthor(modifySelectInfo.author);
    setContent(modifySelectInfo.content);
    setCreateAt(modifySelectInfo.createdAt);
  }, [
    modifyMode,
    modifySelectInfo,
    setAuthor,
    setContent,
    setCreateAt,
    setProfileURL,
  ]);

  // 수정모드가 꺼졌을 때 입력 폼 초기화
  useEffect(() => {
    if (!modifyMode) {
      inputReset();
    }
  }, [modifyMode, inputReset]);

  return (
    <FormStyle>
      <form>
      ...
        <button type="submit" onClick={onSubmitComment}>
          {modifyMode ? "수정하기" : "등록하기"}
        </button>
        {modifyMode && <div>선택된 id : {modifySelectInfo.id}</div>}
      </form>
    </FormStyle>
  );
}
const Comment = ({ info, index }) => {
  const dispatch = useDispatch();
  const { comments } = useSelector((state) => state.comment);
  const { goToPage1 } = useComment();

  const onChageModifyMode = () => {
    dispatch(SET_MODIFY_MODE(comments[index].id)); //수정 모드를 전역적으로 관리
    dispatch(SET_MODIFY_SELECTED_INFO(info));
  };

  const onRemoveComment = (index) => {
    removeComment(comments[index].id);
    goToPage1(); //삭제 후 1페이지로 이동
  };

  return (
    <CommentDiv>
      ...
      <Button>
        <button onClick={onChageModifyMode}>수정</button>
        <button
          onClick={() => {
            onRemoveComment(index);
          }}
        >
          삭제
        </button>
      </Button>
      <hr />
    </CommentDiv>
  );
};

Hooks

  • useComment Hook을 사용하여 댓글 Create 및 Delete 이후, 1 페이지로 이동하도록 구현
import { useDispatch } from "react-redux";
import { GET_COMMENTS_CURRENT_PAGE } from "../slice/thunk/comment";

const useComment = () => {
  const dispatch = useDispatch();

  const goToPage1 = () => {
    dispatch(GET_COMMENTS_CURRENT_PAGE(1));
  };

  return { goToPage1 };
};

export default useComment;

2. Pagination


  • json-server 라이브러리를 사용하여 구현
    • API 호출 예시:
      • 한페이지에 4개의 게시물이 보이고, 최근 게시물부터 정렬해서 3페이지를 보고 싶은 경우
        ➡️ GET /comments?_page=3&_limit=4&_order=desc&_sort=id

Component

  • map 메서드를 사용하여 전체 페이지 버튼들을 생성한 후, 각 페이지 버튼을 누를 때마다 dispatch( 'GET_COMMENTS_CURRENT_PAGE' )를 보내 해당 페이지의 정보를 가져오도록 구현
function PageList() {
  const dispatch = useDispatch();
  const { currentPageNumber, commentsLength } = useSelector(
    (state) => state.comment
  );

  const onPageMove = (e) => {
    console.log(e.target.innerHTML);
    dispatch(GET_COMMENTS_CURRENT_PAGE(e.target.innerHTML));
  };

  return (
    <PageListStyle>
      {Array(commentsLength)
        .fill()
        .map((_, index) => (
          <Page
            active={index + 1 === currentPageNumber}
            key={"key" + index}
            onClick={onPageMove}
          >
            {index + 1}
          </Page>
        ))}
    </PageListStyle>
  );
}

api

export const getCommentsPagination = async pageNumber => {
  return await instance.get(`/comments?_page=${pageNumber}&_limit=5&_order=desc&_sort=id`);
};

3. 리덕스 비동기 처리


  • 리덕스 슬라이스 파일 내에 비동기 처리
extraReducers: builder => {
    builder.addCase(GET_COMMENTS_LENGTH.fulfilled, (state, action) => {
      const commentsLength = action.payload.length / state.pageLimit;

      if (Number.isInteger(commentsLength)) {
        state.commentsLength = commentsLength;
      } else {
        state.commentsLength = Math.ceil(commentsLength);
      }
    });

    builder.addCase(GET_COMMENTS_CURRENT_PAGE.fulfilled, (state, action) => {
      state.comments = action.payload.comments;
      state.currentPageNumber = Number(action.payload.pageNumber);
    });

    builder.addCase(ADD_COMMENT.fulfilled, state => {
      state.currentPageNumber = 1;
    });
  },
  • 비동기 처리 파일은 따로 빼둠
export const GET_COMMENTS_LENGTH = createAsyncThunk(
  "GET_COMMENTS_LENGTH",
  async () => {
    const res = await getComments();
    return res.data;
  }
);

export const GET_COMMENTS_CURRENT_PAGE = createAsyncThunk(
  "GET_COMMENTS_CURRENT_PAGE",
  async (pageNumber) => {
    const res = await getCommentsPagination(pageNumber);
    const comments = res.data;
    return { comments, pageNumber };
  }
);

export const MODIFY_COMMENT = createAsyncThunk(
  "MODIFY_COMMENT",
  async (info) => {
    await modifyComment(info);
  }
);

export const ADD_COMMENT = createAsyncThunk("ADD_COMMENT", async (info) => {
  await addComment(info);
});

About

원티드 온보딩 인턴십 4주차 과제

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published