본문 바로가기
coding/react

async-await function in useEffect

by 코딩희송 2022. 2. 22.

useEffect는 일반적으로 리액트에서 데이터 가져오기가 일어나는 곳이다.
데이터 가져오기는 비동기 함수를 사용하는 것을 의미하고 useEffect를 사용하는 것은 생각만큼 간단하지 않을 수 있다.

잘못된 방법

useEffect에서 데이터 가져오기 수행하는 잘못된 방법 한가지:

...
// ❌ 이렇게 하지 마세요
useEffect(async () => {
    const data = await fetchData();
}, [fetchData])
...

리턴하는 데이터가 소리를 지르는 코드가 될 것이다.
여기서 문제는 useEffect의 첫번째 인수가 아무것도 반환하지 않거나(정의되지 않음) 함수를 반환하는 함수여야 하는데,
비동기 함수는 함수로 호출할 수 없는 Promise를 반환한다.
useEffect 후크가 첫 번째 인수에 기대할 수 없다.

그렇다면 useEffect 내에서 비동기 코드를 어떻게 사용할까?

일반적으로 해결책은 useEffect 자체 내부에 데이터를 가져오는 것이다.:

...
useEffect(() => {
    // 데이터 패치 함수 선언
       const fetchData = async () => const data = await fetch('~');

    // 함수 호출
    fetchData()
        // 에러 핸들링
        .catch(console.error);
}, [])
...

주의할 점 -> 비동기 코드의 결과를 사용하려면 외부가 아니라 fetchData 함수 내부에서 사용해야 한다는 것.
이렇게 사용할 경우 아래와 같은 문제가 발생할 수 있는데, :

useEffect(() => {
    const fetchData = async () => {
        const data = await fetch('~');
        const json = await data.json();
        return json;
    }

    const result = fetchData()
        .catch(console.error);

    // 예상대로 동작하지 않음    
    setData(result);
}, [])

결과는 팬딩 된 Promise.

Promise {<pending>}

그렇다면 useEffect내에서 비동기 코드의 결과를 어떻게 사용해야할까?

useEffect(() => {
  const fetchData = async () => {
    const data = await fetch('~');
    const json = await response.json();
    setData(json); // 여기!
  }

  fetchData()
    .catch(console.error);;
}, [])
}, [])

useEffect 외부에서 함수를 추출해야하는 경우는 어떻게하지?

어떤 경우에는 useEffect 외부에서 데이터 가져오기 기능을 원한다.
이 경우 useCallback으로 함수를 래핑하는데 종속성 배열에 주의해야한다.

useCallback에 래핑되지 않은 경우 다시 렌더링할 때마다 업데이트되므로 재렌더링할 때마다 useEffect가 트리거된다.

· · ·
// declare the async data fetching function
const fetchData = useCallback(async () => {
  const data = await fetch('~');

  setData(data);
}, [])

// useEffect는 fetchData를 적시에 호출하기 위해서 존재하게 된다.
useEffect(() => {
  fetchData()
    // make sure to catch any error
    .catch(console.error);;
}, [fetchData])
· · ·

더 이상 setData를 종속성 배열에 넣을 필요가 없게 됐다.

useEffect의 경우 콜백 실행을 의미.
useCallback의 경우 훅에서 반환되는 함수를 변경하는 것을 의미.

useEffect 내부에서 데이터 패치시 참고사항

호출이 매개변수에 의존한다고 가정해보자:

· · ·
useEffect(() => {
  // declare the async data fetching function
  const fetchData = async () => {
    // get the data from the api
    const data = await fetch(`https://yourapi.com?param=${param}`);
    // convert the data to json
    const json = await response.json();

    // set state with the result
    setData(json);
  }

  // call the function
  fetchData()
    // make sure to catch any error
    .catch(console.error);;
}, [param])
· · ·

param이 값을 변경하면 fetchData가 2번 호출된다.
이것이 빨리 발생하면 첫번째 호출이 두번째 호출 후에 리졸브될 수 있으므로 이전 값을 유지하게 된다.
그 문제를 해결하는 방법은 상태를 업데이트할지 여부를 제어하는 변수를 갖는 것이다.:

· · ·
useEffect(() => {
  let isSubscribed = true;

  // declare the async data fetching function
  const fetchData = async () => {
    // get the data from the api
    const data = await fetch(`https://yourapi.com?param=${param}`);
    // convert the data to json
    const json = await response.json();

    // 'isSubscribed'가 true일 때, 상태값을 업데이트함.
    if (isSubscribed) {
      setData(json);
    }
  }

  // call the function
  fetchData()
    // make sure to catch any error
    .catch(console.error);;

  // isSubscribed 초기화
  return () => isSubscribed = false;
}, [param])
· · ·

이것은 여러번 트리거 될 수 있는 useEffect에서 데이터를 가져올 때 사용할 수 있는 일반적인 패턴이다.

댓글