본문 바로가기

Frontend Study - 2/React

React : useEffect에서 async 쓰기.

아.. 너무 고됐다.

잘못된 구글링이 얼마나 위험할 수 있는지 느꼈던 경험이었다. 

시작 단추를 잘 못 끼워 맞춰서 한참을 해맸다. 

 

stackoverflow에 아래의 예시와 같이 써진 코드를 참고해서 작성하는데, 한참을 해도 작동이 안됐었다. 

useEffect(async () => {
  const data = await fetchData();
}, [fetchData])

 

작동이 안됐던 이유는, useEffect의 첫번째 인자는 undefined 혹은 함수를 리턴하는데,

async함수는 Promise를 리턴하기 때문이라고 한다.

 

이것을 해결하기 위한 방법은, 비동기 함수 선언을 useEffect안에 넣어버리는 것이다. 

useEffect(() => {
  // declare the data fetching function
  const fetchData = async () => {
    const data = await fetch('https://yourapi.com');
  }

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

 여기서 한가지 더 주의해야 할 점은,

fetch해온 결과 값을 이용하고 싶을 때도 마찬가지로 비동기함수(fetchData함수) 안에 넣어야 한다는 것이다.

 

 

아래와 같이 밖에서 사용하는 것을 주의해야 한다 !

useEffect(() => {
  // declare the async data fetching function
  const fetchData = async () => {
    // get the data from the api
    const data = await fetch('https://yourapi.com');
    // convert data to json
    const json = await data.json();
    return json;
  }

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

  // ❌ don't do this, it won't work as you expect!
  setData(result);
}, [])

 

setData(result)가 실행될 때 result의 값이 어떤 값일지 확신할 수 없기 때문이다.

result의 값은 프로미스의 펜딩상태를 유지하게 된다. 콘솔로 result를 확인해 본다면 아래와 같은 결과를 보게 된다.

 

Promise {<pending>}

 

setData(result)를 비동기함수 '안'에 넣음으로서 문제를 해결할 수 있었다. 

 

useEffect(() => {
  // declare the async data fetching function
  const fetchData = async () => {
    // get the data from the api
    const data = await fetch('https://yourapi.com');
    // 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);;
}, [])

 

만약 어떤 이유에 의해 결과 값을 이용하는 코드를 밖에서 사용하고 싶다면 방법이 없는 것은 아니다.

useCallback 을 사용하는 방법이다.

왜 useCallback을 사용해야 하냐면, 

useEffect밖에서 함수를 선언하게 되면 dependency안에 해당 함수를 넣어야 한다. 그런데 이것이 무한렌더링을 초래하기 때문이다.

useCallback 은 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값 (dependency) 이 변경될 때까지 재렌더링 시키지 않는다. 

 

const fetchData = useCallback(async () => {
  const data = await fetch('https://yourapi.com');

  setData(data);
}, [])

// the useEffect is only there to call `fetchData` at the right time
useEffect(() => {
  fetchData()
    // make sure to catch any error
    .catch(console.error);;
}, [fetchData])

 

내부에서 setData해준 코드로 돌아가서,

data를 fetch해오는 주소값에 들어갈 param이 있다면, param을 디펜던시 안에 넣음으로서 동적으로 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 함수가 두번 호출되게 되는데, 이 과정이 빠르게 일어난다면 race condition 문제가 생기게 된다. race condition 문제란 첫번째 호출 이후 두번째 호출이 일어났음에도 첫번째 호출 값으로 상태가 유지되는 것이다. 

 

이 때는 아래와 같이 상태값을 없데이트 할 지 말지 결정하는 변수를 만들어서 해결할 수 있다. 

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();

    // set state with the result if `isSubscribed` is true
    if (isSubscribed) {
      setData(json);
    }
  }

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

  // cancel any future `setData`
  return () => isSubscribed = false;
}, [param])

 

참고사이트

https://devtrium.com/posts/async-functions-useeffect