React 9분 읽기

서버에 요청하기

위의 두 컴포넌트에서 필터 요소나 페이지를 클릭하게 되면 쿼리스트링을 바꿔 데이터 요청을 할 수 있게 했습니다.

서버 요청은 어디서 하지?

아고라스테이츠를 리액트로 만들면서 서버에 요청을 보내는 경우가 종종 있었습니다. 여기서 우리가 고려할 것은 useEffect훅을 사용하는 것입니다. 리액트에서 데이터 패칭과 같은 사이드 이팩트를 다루는 코드는 보통 useEffect훅 내에 작성하게 됩니다. 그렇다면 항상 useEffect가 서버 요청에 필요한 훅일까요?

useEffect훅을 이용하기

제가 아고라스테이츠 리액트에서 사용한 방법은 아래와 같습니다.

useEffect(() => {
  (async () => {
    console.log("fetching...");
    const res = await fetchDataWithQuery("discussions", { ...queryObj });
    setDiscussions(res.discussion);
    setPages(res.pages);
  })();
}, [queryObj]);

queryObj라는 쿼리스트링의 정보를 객체형태로 담은 상태가 변할 경우 그 쿼리스트링을 가지고 데이터 패칭을 하는 경우입니다.

위의 두 컴포넌트에서 필터 요소나 페이지를 클릭하게 되면 쿼리스트링을 바꿔 데이터 요청을 할 수 있게 했습니다.

버튼 클릭 시엔 어떻게 할까?

위의 경우와 비슷하긴 하지만 일단 아래의 코드를 봅시다.

// 버튼 클릭 시 useEffect 사용

const [submit, setSubmit] = useState(false);
const onSubmitClick = () => {
	setSubmit(true); 
}
useEffect(() => {
  	submit &&
	(async () => {
      await fetchDataWithBody("discussions", {
        method: "POST",
        mode: "cors",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          id: Date.now(),
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
          title: titleRef.current.value,
          author: user,
          bodyHTML: contentRef.current.value,
          tag: tagRef.current.value,
          avatarUrl: "./asset/user.png",
          answer: null,
        }),
      });

      initFetch();
      setShow((prev) => !prev);
    })();
}, [submit]);

특정 버튼을 눌렀을 때 데이터를 패치해야 한다면 useEffect를 꼭 사용할 필요가 있는 지 고민해봐야할 것입니다. 실제로 위의 코드에서 저는 처음에 useEffect를 저렇게 사용해서 데이터 패치를 구현했습니다. 하지만, 굳이 useEffect사용이 필요할까하는 의문이 계속 있었고, 실제로 저와 비슷한 고민을 한 사람의 질문을 스택 오버플로우에서 찾을 수 있었습니다.

https://stackoverflow.com/questions/62277013/fetch-data-from-an-api-in-useeffect-or-in-event-handler-directly-in-react

이 글의 답변 내용은 다음과 같습니다.

It depend on your usecase as to which method you should take

fetching data in useEffect is useful in following scenarios

  • Fetching data during some lifecycles like initial render
  • Fetching data when some prop changes
  • fetching data in an interval but setting up a subscription or setInterval

Fetching data in handler is useful in below scenario

  • Based on a user interaction such as search button click, search input change

Since your case is a based on a use interaction its better and more controlled to actually call the API in a handler instead of a useEffect hook

usecase를 고려해서 사용하면 된다고 하는데, 보통 데이터 패칭을 할 때 useEffect를 사용하는 경우는 특정 라이프사이클에서 데이터를 가져오거나, 특정 props가 변경되었을 때, 일정 간격으로 데이터 패칭을 할 때라고 합니다. 생각해보면 useEffect가 동작하는 방식과 연관이 많은 것 같죠.

핸들러 함수에서 데이터 패칭을 하는 게 좋은 경우는 사용자의 인터렉션에 기반으로 동작하는 경우가 있다고 합니다. 그래서 위의 코드를 아래와 같이 수정하였습니다.

// 버튼 클릭 핸들러 내부에서 데이터 패치
const onSubmitClick = () => {
    (async () => {
      await fetchDataWithBody("discussions", {
        method: "POST",
        mode: "cors",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          id: Date.now(),
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
          title: titleRef.current.value,
          author: user,
          bodyHTML: contentRef.current.value,
          tag: tagRef.current.value,
          avatarUrl: "./asset/user.png",
          answer: null,
        }),
      });

      initFetch();
      setShow((prev) => !prev);
    })();
  };

사실 내부 코드는 달라질 게 없고, 그냥 클릭 핸들러에서 사용하냐 아니냐의 차이일 뿐이었습니다.

근데 페이지네이션이나 필터는?

그리고 필터나 페이지네이션도 클릭 시 데이터 요청이 다시 이루어지는 것이기 때문에 위와 비슷하게 수정이 필요할까 생각을 했습니다. 제가 내린 결론은 그렇지않다 였습니다. 이유는 위와 달리 동작이 같았기 때문입니다. 단지 query string으로 들어가는 내용만 다르지 이후 동작은 같아야 합니다.

(async () => {
  console.log("fetching...");
  const res = await fetchDataWithQuery("discussions", { ...queryObj });
  setDiscussions(res.discussion);
  setPages(res.pages);
})();

내부 함수를 보면 위와 같습니다. 데이터를 쿼리에 맞게 패칭하고 디스커션 상태와 페이지 상태를 변경합니다. 즉, 쿼리 스트링이 바뀌면 위 동작이 계속해서 일어나게 하면 된다고 생각했기 때문에 useEffect를 이용했습니다. 그렇지 않으면 위와 같은 코드를 각각 또 작성해야 한다고 생각하기 때문이죠.

그래도 고려할 것

https://beta.reactjs.org/learn/keeping-components-pure#where-you-can-cause-side-effects

위 글을 참고하시면 이런 내용이 있습니다.

제가 생각한 것처럼 특정 이벤트가 발생했을 때 데이터 패칭을 useEffect에 작성하는 것은 최후의 수단으로 생각하라고 합니다.