본문 바로가기

Solution & What I learn

[React, TypeScript - Project] 독서 기록 서비스 구현하기 | Issue & Solution

반응형

- response된 book 데이터 타입지정

 

 

Instantly parse JSON in any language | quicktype

 

app.quicktype.io

- 위 사이트에서 response된 데이터 타입을 추론하였다.

 

 

 📚 problem & Solution

 

1. edit container에서 클릭된 book 데이터 받기

 

- api에서 직접 book 데이터를 받아오지 않고 (불필요) url의 params를 얻어서 해당 book의 id를 이용하여 전체 데이터인 book에서 find 메서드로 book 데이터를 출력했다.

 

 

2. error

 

 

<Link to={url} target="_BLANK" className={styles.link_url}>
  <button type="button" className={styles.button_url}>
    주소
  </button>
</Link>

 

- to={`/${url}`} => 작성 시 오류는 없어졌지만 원하는 동작은 /뒤에 url이 붙어야 하는 코드가 아니기 때문에 이 방법은 포기

- to={url || `/`}  => url 또는 / 로 오류 없앰

 

문제는 왜 url로 이동하지 않고, http://localhost:3000/url 로 이동되는지 모르겠다.. url만 가져가라고.. 

 

 

3. onChange, value를 사용하지 않고 useRef로 대신하기 ??

 

- form에서 매번 onChange와 value를 사용해왔는데, 이번 프로젝트에서는 ref를 사용하는 방식을 사용해봤다. 

두 가지 방식을 알게 되어 각각 특별하게 사용되는 기준이 있는지 리뷰어님께 질문을 드렸는데,

 

 

ref를 사용하는 방식을 흔히 uncontrolled component, onChange와 value를 사용하는 방식을 controlled 방식이라고 지칭하는데 무엇이 낫다고 단정 짓기보다 필요에 따라 적절하게 사용하면 된다.

라는 조언을 받았다.! 이렇게 또 배우기.. ✨

새로 알게 된 개념이라서 조만간 uncontrolled component와 controlled component를 공부해서 블로그에 정리해야겠다.

 

 

4. key error

 

<Table
  dataSource={books || []}
  columns={[
    {
      title: 'Book',
      dataIndex: 'book',
      key: 'book',
      render: (text, record) => (
        <Book
          {...record}
          key={record.bookId}
          deleteBook={deleteBook}
          goEdit={goEdit}
        />
      ),
    },
  ]}
  loading={books === null || loading}
  showHeader={false}
  className={styles.table}
  rowKey="bookId"
  pagination={false}
/>

 

- table의 key값과 Book컴포넌트의 key 값을 모두 지정해줬는데 list 수정 시

 

 

 

- key 값을 안 넣었다는 error가 뜬다...

 

 

해결

 

- 창피하지만 3일 동안 이 에러만 붙잡고 있었던 것 같다. devtools에서도 erorr는 뿜어내지 않아서 대체 뭐가 잘못된 건지 너무 답답했다.. 수정은 제대로 되지만 에러는 없어지지 않는 이상한 상황..(차라리 수정도 되지 말던가..)

 

devtools에서 state가 어떻게 변하고 있는지 액션마다 하나하나 확인한 결과, 수정하려는 책 한권만 수정이 되어야 하는데, 인덱스 하나에 책 목록 전체가 들어가 있는 상황을 발견했다.. 보자마자 이거다 싶어서 saga함수를 바로 확인했는데 역시나 edit saga함수에서 수정 성공 시, return 되는 코드를 잘못 작성해뒀었다. (여태 다른 코드 잡고 씨름한 거 생각하면 잠깐 기절하고 싶다. 😄🔫) 

 

 

* 수정 전

 

function* editBookSaga(action: editBookSagaAction) {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const bookId = action.payload.bookId;
    const book = action.payload.book;
    const bookData = yield call(BookService.editBook, token, bookId, book);
    const books: BookResType[] = yield select(getBooksFromState);
    yield put(
      success(books.map((book) => (book.bookId === bookId ? bookData : books))),
    ); 
   //1. bookData의 id와 비교되어야하는데 그냥 id라니.. 
   //2. 그냥 book을 return해야하는데 전체 책 목록인 books를 return해버리는 대참사
    yield put(push('/'));
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}

 

 

*수정 후

 

function* editBookSaga(action: editBookSagaAction) {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const bookId = action.payload.bookId;
    const book = action.payload.book;
    const bookData = yield call(BookService.editBook, token, bookId, book);
    const books: BookResType[] = yield select(getBooksFromState);
    yield put(
      success(
        books.map((book) =>
          book.bookId === bookData.bookId ? bookData : book,
        ),
      ),
    );
    yield put(push('/'));
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}

 

- 알고 보니 정~말 별 거 아니었다.. 대부분 에러의 해결은 '알고 보니 별거 아니었다'지만.. 이렇게 또 해결했다.

 

 


 

 

🔨 what i learn

 

1. API 응답을 확인하여 response 된 데이터를 interface로 props의 타입을 지정하는 법을 배웠다.

2. 이 프로젝트 전에는 '타입을 지정하면 오히려 코드가 더 길어지고, 일일이 타입 지정이 번거롭지 않을까' 했는데 전달하지 않은 props의 오류를 바로 확인할 수 있어 실수를 줄일 수 있었고, 해당 변수나 코드가 무슨 코드인지 쉽게 확인이 가능하다는 것이 타입스크립트의 장점인 것을 느꼈다. 

3. uncontrolled component / controlled component의 방식을 알게 되었다.