Search Page (Context API 사용의 잘못된 예)

Ava Kim·2022년 6월 12일
0

도서 검색 페이지 리팩토링하기

문제 정의하기

Search page는 기존에 팀 멤버인 R이 담당했던 부분이다.

// 검색페이지 구조

<SearchPage>
	<SearchBar />
	<SearchWidget />
</SearchPage>

사용자 input 값(도서명 또는 저자명)을 SearchBar에서 받아와서 Google Books API query에 전달하고, 그 결과값을 Search Widget에서 렌더링하는 구조이다.
여기서 R은 API 통신으로 받아온 데이터 배열을 Context를 사용해서 SearchWidget에 넘기는 방식을 사용했다.

정말 부끄럽지만 이 때까지만 해도 Context에 대해 잘 몰라 나름 사용한 이유가 있겠거니 하고 물어 볼 생각을 안 했었다. 이번에 리뷰하면서 다시 살펴보니 parent인 SearchPage에 받아온 데이터를 useState로 업데이트하고, top-down으로 전해주면 되지 않을까 하는 생각이 들었다.

이렇게 의견을 전달하니까 R이 당시에 Context에 대해서 막 배웠을 때라 어떻게든 사용해 보고 싶었고 (나도 배우면 일단 써먹어봐야 하는 사람이라 너무 공감했다ㅋㅋㅋ) 본인은 Context를 Redux Store처럼 생각했었다고 했다.

논의 끝에 굳이 Context를 사용할 이유가 없는 부분이어서 삭제하고, useState를 사용해 top-down으로 데이터를 내려주는 방식으로 변경하기로 했다.

변경 전

변경하기 전 코드는 아래와 같았다. Context가 잘못 사용된 것과 더불어 axios를 이용한 API 통신이 handler 함수 내에서 이루어지는 것도 마음에 걸렸다. 나중에 axios를 사용하지 않게 되거나 변경하게 됐을 때 업데이트가 용이하도록 하고, 데이터 관련한 로직도 View와 구분이 되도록 별도 클래스 파일로 빼기로 했다.

const Search = () => {
  const bookResultContext = useContext(SearchContext);
  const [searchValue, setSearchValue] = useState('');

  const getSearchValueHandler = (e) => {
    setSearchValue(e.target.value);
  };

  const searchHandler = (e) => {
    e.preventDefault();
    //Max result indicates the amximum results of search Max number is 40

    axios
      .get(`${searchValue}&maxResults=40`)
      .then((response) => {
        let results = response.data.items;
        bookResultContext.setResult(results);
      })
      .catch((err) => {
        console.log('error', err);
        bookResultContext.setResult([]);
      });
  };
  return (
    <Layout>
      <SearchBar
        submit={searchHandler}
        search={getSearchValueHandler}
      />
      <SearchWidget />
    </Layout>


변경 후

//searchPage.js

const client = axios.create({
  baseURL: 'https://www.googleapis.com/books/v1/volumes?q=',
});

const googleBooks = new GoogleBooks(client);

const Search = () => {
  const [bookList, setBookList] = useState([]);

  const handleSearch = (value) => {
    googleBooks
      .search(value)
      .then((results) => setBookList(results))
      .catch((error) => {
        console.log(error);
        setBookList(null);
      });
  };

  return (
    <Layout>
      <SearchBar onSearch={handleSearch} />
      <SearchedList
        bookList={bookList}
        googleBooks={googleBooks}
        onAdd={addList}
      />
    </Layout>
  );
};

export default Search;

//searchPresenter.js /

class GoogleBooks {
  constructor(client) {
    this.googleBooks = client;
  }

  async search(query) {
    const response = await this.googleBooks.get(`${query}`, {
      params: {
        maxResults: 40,
      },
    });
    return response.data.items;
  }

  trimList(list) {
    const id = list.id;
    const info = list.volumeInfo;
    const title = info.title ? info.title : '';
    const author = info.authors ? info.authors : 'unknown';
    const thumbnail = info.imageLinks?.thumbnail;
    const imgUrl = thumbnail ? thumbnail : null;
    return { id, title, author, thumbnail, imgUrl };
  }
}

리액트 Context 정리

이번에 리팩토링 하면서 공식 문서와 다른 글들 참조해서 Context를 다시 공부했다.

참조한 글)
리액트 공식 문서: https://reactjs.org/docs/context.html#when-to-use-context
벨로그 포스트: https://velog.io/@jheeju/리액트-Context-는-상태-관리-도구가-아니다

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

공식 문서에 따르면 Context는 모든 레벨에 매뉴얼하게 props를 내려줄 필요 없이, 즉 prop-drilling을 피해 컴포넌트에 데이터를 전달하는 방법을 제공한다.

When to Use Context

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult.

Context는 컴포넌트 트리 안에서 ‘글로벌'하다고 간주되는 데이터를 공유하도록 디자인 되었는데, 현재 로그인한 유저나 테마, 선호 언어 등이 그 예이다. Context는 특정 데이터가 다른 레벨에 네스팅되어 있는 많은 컴포넌트들에서 모두 접근할 필요가 있을 때 사용되어 진다고 명시되어 있다.


위 내용을 바탕으로 다시 정리해 보면,

Google Books API에서 받아온 데이터 배열은 Search Page, 그것도 SearchedList(이전 Search Widget)에서만 사용되므로

  • 데이터가 글로벌하다고 볼 수 없고,
  • 다른 레벨에 네스팅 되어 있는 많은 컴포넌트들에서 접근할 필요가 없고,
  • prop-drilling이 일어나지 않으므로 (한 번만 전달하면 됨)

Context를 Search page에서 사용할 이유가 전혀 없다.

결론 - 배운 점

  1. 어떤 도구를 사용할 때는 사용하려는 용도에 맞는 것인지, 다른 방법은 없는지 꼭 확인하자.

  2. 내가 짠 코드가 아니더라도 자세히 보고 고민하고, 더 좋은 방법이 있다면 의논하자. (아니면 내가 생각 못 했던 이유가 있을 수도 있으니 물어보고 배우자)

  3. 이번 리팩토링 할 때, Authenticated user에 Context를 사용하자

profile
FE developer | self-believer | philomath

0개의 댓글