[Main Project] UDog / 구현하기 - 더보기 기능, 좋아요 기능 구현하기

soohyunee·2023년 3월 15일
0

[Main Project] UDog

목록 보기
8/18

1. 구현하기

진행 상황

  • 미용실 좋아요
  • 미용실 소개글 더보기 기능

진행 예정

  • 좋아요 기능

2. TIL

2-1. 더보기 기능 구현하기

커스텀 훅으로 코드 분리하기

미용실 상세 페이지에서 미용실 소개 코멘트 박스를 만들었다. 코멘트의 길이가 미용실 마다 다르면 UI가 통일 되지 않아 보여서 글자 길이의 limit를 정해서 limit를 넘는 글자는 더보기 버튼이 보이고 나머지 글자는 ... 으로 표시가 되게끔 구현하고 싶었다.

{show ? data.hairShopDescription : `${data.hairShopDescription.slice(0,63)} ...`}

위와 같이 코드를 작성했는데 저번 string 메서드를 사용했을 때 사용할 수 없다라는 에러가 떴다.

시도한 방법

이것 역시 get으로 데이터를 받아오는 동안 값이 undefined여서 발생하는 에러라고 생각이 들어서 부모 컴포넌트에 적용 했지만 그래도 같은 에러가 발생했다.

const data = useFetch(`/hair-shops/${id}`);

const tabContent = {
  0: <HomeTab data={data} />,
  1: <BookTab />,
  2: <ReviewTab />
};

해결 방법

그래서 그때와 비슷한 방법으로 data가 먼저 있는지 확인 후 show 상태에 따라 다르게 값이 보여지게 작성했더니 에러가 해결되었다.

<S.CommentText className={show ? '' : 'hide'}>
{data && data.hairShopDescription ? (show ? data.hairShopDescription : `${data.hairShopDescription.slice(0,63)} ...`) : ''}
</S.CommentText>

하지만 더보기 박스를 조금 더 고도화 하여서 구성을 해보니, 코드가 많이 길어지게 되었다. 우선 data가 받아졌는지 -> 접기 상태에서 63자를 넘으면 63자 이후에는 ...이 보이고 -> 접기 상태에서 63자가 안넘으면 콘텐츠 전체가 보이고 -> 더보기 상태라면 코멘트가 전부 보여야한다.
그리고 show를 통해서 버튼의 텍스트도 접기와 더보기로 바꾸고, 글자가 63자가 안넘으면 더보기 버튼 자체가 보이지 않게 바꾸어주었다.
그래서 커스텀훅을 활용해서 해당 코드를 조금 더 짧고 간결하게 바꿀 수 있었다.

const {show, handleToggle, comment, getDisplayComment} = useComment(data.hairShopDescription);

{
  data && comment ? (
    <>
      <S.CommentTitleBox>
        <S.CommentTitle>매장 소개</S.CommentTitle>
        {comment.length > 63 && (
          <S.CommentButton onClick={handleToggle}>
            {show ? "접기" : "더보기"}
          </S.CommentButton>
        )}
      </S.CommentTitleBox>
      <S.CommentText className={show ? "" : "hide"}>
        {getDisplayComment(comment)}
      </S.CommentText>
    </>
  ) : null;
}

2-2. 좋아요 기능 구현하기

상태에 따른 axios 요청

api 명세서에서 좋아요 등록과 좋아요 취소 모두 post를 사용하는 방법으로 작성되어있었다. 그래서 좋아요 등록과 취소 요청을 하는 post 부분을 훅으로 따로 분리하여 로직을 작성하였다.
우선 좋아요 버튼의 상태를 리덕스를 활용하여 불리언값으로 관리하고 상태에 따라 if문으로 다른 post를 보내게끔 작성하였다.
요청은 잘갔지만 문제는 한번 클릭했을 뿐인데 요청이 너무 많이 가는 것이었다.

시도한 방법

좋아요 상태만으로 요청을 처리하니 상태가 유지 되는 만큼 요청이 중복해서 보내지는 것 같았다. 그래서 리덕스로 좋아요 상태만이 아닌 다른 상태 변수를 활용해서 요청이 진행 중인지 아닌지를 관리하고, 그 상태로 중복 요청을 막아보았다.
요청을 보내기 전에 true로 설정하여 중복 요청을 방지하고, 요청이 완료되면 false로 설정하여 다시 요청할 수 있도록 했다. 그리고 이미 진행 중인 요청이 있는 경우 바로 함수가 종료되게 처리해주었다.

if (isSubmit) {
	return;
}
dispatch(setIsSubmit(true));

if (!like) {
  API.post(
    // ... 
    .finally(() => {
      dispatch(setIsSubmit(false));
    });
} else {
  API.post(
    // ...
    .finally(() => {
      dispatch(setIsSubmit(false));
    });
}

위의 방법대로 해주니 중복요청을 막을 수 있었다. 하지만 좋아요와 좋아요 취소 요청을 했을 때 상태가 변하는게 반영이 되지 않았다. 강아지 정보 삭제 했을 때 리덕스로 상태를 업데이트 시켜주어던 것이 생각나서 미용실 정보도 createAsyncThunk로 받아왔지만 값이 들어오지 않았다.

const {id} = useParams();

const asyncUpFetch = createAsyncThunk('shopSlice/asyncUpFetch', async () => {
	const response = await API.get(`/hair-shops/${id}`);
	return response.data;
});

해결 방법

구글링을 해보니 리덕스에서는 react-router 관련 훅이 사용이 안되는 것이었다. 그래서 useParams로 id값을 얻어오지 못해서 초기값인 빈 배열만 나오는 것이었다. 그래서 createAsyncThunk 에 대해 조금 더 자세히 공부해본 결과 해결할 수 있는 방법이 있었다.

createAsyncThunk : 비동기 작업을 처리하는 Redux Thunk 함수를 생성하기 위해 사용하는 함수로, 세 가지 인자를 받음
1. typePrefix : thunk 함수에서 수행할 작업을 식별하는 문자열
2. payloadCreator : thunk 함수가 호출될 때 실행할 비동기 작업을 수행하는 함수, 두 개의 인자를 받음

  • payload : thunk 함수를 호출할 때 제공되는 인자
  • thunkAPI : dispatch, getState 및 extra라는 세 가지 속성을 포함하는 객체
    a. dispatch : 액션을 실행할 수 있음
    b. getState : Redux store의 현재 상태를 가져올 수 있음
    c. extra : createAsyncThunk 함수 호출 시 제공한 extraReducers 속성에서 추가로 사용할 reducer를 지정할 수 있음
  1. options (선택 사항) : thunk 함수에서 사용할 옵션을 지정하는 객체, options 객체는 다양한 속성을 포함할 수 있으며, 가장 일반적인 속성은 condition 및 extraReducers
  • condition : thunk 함수가 호출될 때 실행되는 조건 함수를 지정
  • extraReducers : thunkAPI의 extra 속성에서 추가로 사용할 reducer를 지정하는 객체

위의 내용을 토대로 우선 id를 저장할 수 있는 리듀서를 만들고, 컴포넌트 내에서 id 값을 dispatch하여 id의 상태를 바꾸어 주었다. 그리고 thunkAPI를 활용하여 그중 getState로 현재 상태를 가져와서 id를 관리하는 리듀서에 접근해 id값을 가져올 수 있었다.
그리고 like를 증가시키고 감소시키는 리듀서를 작성하여 post로 요청을 하고 응답을 받아올 때 dispatch로 액션을 실행시켜 주었다.

useEffect(() => {
  dispatch(setId(id));
  dispatch(asyncUpFetch());
}, [dispatch]);
const asyncUpFetch = createAsyncThunk('shopSlice/asyncUpFetch', async (_, thunkAPI) => {
	const {id} = thunkAPI.getState().set;
	const response = await API.get(`/hair-shops/${id}`);
	return response.data;
});

//..

reducers: {
  addLike: (state) => {
    state.value.likeCount++;
  },
  cancleLike: (state) => {
    state.value.likeCount--;
  },
},
profile
FrontEnd Developer

0개의 댓글