본문 바로가기
FrontEnd/React.js

[React.js] useContext 찍먹, useFetch 구현, loading, error 상태 관리

by Chaedie 2022. 10. 31.
728x90

useContext를 찍먹해봤습니다.

원티드 프리온보딩 과제 중 useContext API를 API와 연동하라는 요구사항이 있어 급하게 학습 => 사용해보았습니다. 시간이 많이 부족해 제대로 contextAPI를 사용하지 못해 많이 아쉽습니다. 그래도 찍먹에 의미를 두고 PR 내용을 블로그에 남깁니다.

이번 과제를 통해 useFetch로 로직 분리 하는 방법을 확실히 배웠습니다. 커스텀 훅을 만드는데 두려움 없는 수준이 된 것 같습니다. 앞으로 컴포넌트에서 로직을 뺄 수 있다면 꼭!!! 커스텀훅으로 빼야겠습니다. 커스텀 훅을 통해 component의 view단이 깔끔해지니 너무 마음에 들어서 앞으로 커스텀 훅을 애용할 것 같습니다.

또한, [isLoading, errors] 상태를 만들어 loading중,axios 에러 시 처리를 해보았습니다. 이런 처리 방법이 있다는 걸 알고는 있었지만 단순 기능 구현을 위해 프로젝트 당시에 별로 신경을 쓰지 않은 부분입니다. 하지만 이런 사소한 처리가 어려운 것도 아니기에 앞으로는 꼭 적용할 예정입니다.

마지막으로, context API를 사용해보았습니다. provider를 사용하여 전역 변수처럼 state를 사용하는 방법만 간단하게 사용해보았습니다. 하지만 인터넷에 다양한 방법으로 context API를 사용하는 것을 보았습니다. 이를 모두 따라하기에 시간이 부족해 전부 경험하진 못했지만 따로 시간을 내어 해당 부분을 경험해볼 예정입니다. 이런 경험 이후에 redux, react-query, swr 등을 차례로 경험하면서 비교 분석하여 프로젝트에서 어떤 상태관리 라이브러리를 사용할지 결정하는데 풍부한 경험을 가지고 결정할 수 있도록 로드맵을 짜야겠습니다.


:: 주요 기능

  1. Context API와 API 연동
  • Context API를 생성, 최상위 컴포넌트에서 Provider로 state를 제공해주었습니다.
  • 또한 state관리의 용이성을 위해 최상위 파일에서 모든 로직을 담당했습니다.
  • useFetch 커스텀 훅을 활용해 Data Fetching 부분을 분리하였습니다.
export interface IssueContextInterface {
  issueList: Issue[];
  isLoading: boolean;
  errors: boolean;
}

export const IssueContext = createContext<IssueContextInterface>(null);
function App() {
  const [issueList, setIssueList] = useState<Issue[]>([]);
  const [page, setPage] = useState(1);
  const [isLoading, errors] = useFetch(setIssueList, page);

  return (
    <IssueContext.Provider value={{ issueList, isLoading, errors }}>
    //...생략
function IssuesPage({ setPage }: Props) {
  const { issueList, isLoading, errors } = useContext(IssueContext);
  //...
}
function useFetch(
  setIssueList: Dispatch<SetStateAction<Issue[]>>,
  page: number
) {
  const [isLoading, setIsLoading] = useState(false);
  const [errors, setErrors] = useState(false);

  const handleFetch = useCallback(
    async (page: number) => {
      setIsLoading(true);
      try {
        const data = await IssuesService.getIssues(page);
        setIssueList(prev => [...prev, ...data]);
      } catch (error) {
        setErrors(true);
      } finally {
        setIsLoading(false);
      }
    },
    [setIssueList]
  );

  useEffect(() => {
    handleFetch(page);
  }, [page, handleFetch]);

  return [isLoading, errors];
}

 

  1. 로딩, 에러 처리
  • errors, isLoading 스테이트를 활용하였습니다.
  • 에러가 있을 경우 <Error / > 컴포넌트가 렌더됩니다.
  • 로딩 중일 경우 <Loading /> 컴포넌트가 렌더됩니다.
  • 구현 초기엔 <Error /> 컴포넌트 바로 다음 <Loading /> 컴포넌트가 위치해있었습니다. 이 경우 무한 스크롤 페칭 중 데이터 부분이 사라지고 로딩으로 변했다 다시 돌아오는 부자연스러운 렌더를 보여주었습니다. 따라서 <Loading /> 컴포넌트는 <IssueItem /> 최하단으로 위치시켰습니다.
if (errors) {
  return <Error />;
}

return (
  <>
    <ul>
      {issueList &&
        issueList.map((issue, idx) => {
          return (
            <li key={issue.number} style={{ listStyle: 'none' }}>
              {idx === 4 && <AdBanner />}
              <IssueItem issue={issue} />
            </li>
          );
        })}
    </ul>
    <div ref={setObserveTarget as any} />

    {isLoading && <Loading />}
  </>
);

 

  1. 무한 스크롤
  • javascript에서 제공하는 intersectionObserver API를 활용해 무한 스크롤을 구현하였습니다.
  • 이슈 페이지 최하단에 observing을 위한 div를 만들어 두고, 해당 div가 intersecting 될 때 setPage(prev => prev + 1)을 통해 page를 넘겼습니다.
  • page를 디펜던시로 하는 useEffect를 만들어 두어 page가 넘어갈 때마다 handleFetch(page)가 실행되도록 하였습니다.
// @src/pages/IssuesPage.tsx
const setObserveTarget = useIntersectionObserver(setPage);
// 생략 ...
return (
  // ... 이슈 리스트 최하단
  <div ref={setObserveTarget as any} />
);
useEffect(() => {
  handleFetch(page);
}, [page, handleFetch]);
// @src/hooks/useIntersectionObserver
export const useIntersectionObserver = (
  setPage: Dispatch<SetStateAction<number>>
) => {
  const [observationTarget, setObservationTarget] = useState(null);

  const observer = useRef(
    new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return;
        setPage(prev => prev + 1);
      },
      { threshold: 1 }
    )
  );

  useEffect(() => {
    const currentTarget = observationTarget;
    const currentObserver = observer.current;
    if (currentTarget) {
      currentObserver.observe(currentTarget);
    }
    return () => {
      if (currentTarget) {
        currentObserver.unobserve(currentTarget);
      }
    };
  }, [observationTarget]);

  return setObservationTarget;
};

 

:: 성장 포인트

  • 컨텍스트 API를 처음 사용해보면서 어디에 Provider를 심어야 할지, 어떻게 Action을 받을지 전혀 감이 없었습니다.
  • 간단한 APP이라 단순히 최상단 컴포넌트에 Provider와 setState 로직을 모아두어 해결했습니다.
  • 다만 더 큰 프로젝트의 경우를 위해 Flux패턴이 구현되어 있는 Redux를 공부해야겠다는 생각을 했습니다.

 

댓글