useState + useEffect, 데이터 처리 삽질

wookhyung·2022년 7월 20일
1

React

목록 보기
4/6

진행중인 프로젝트에서 지역 별 필터링 기능이 있는데, 유저가 선택한 지역에 따라 메인 페이지에 나오는 결과가 변경 되어야 하는 상황이다. 생각했던 방법은 간단하게 1. 유저가 클릭한 지역의 innerText를 가져와서 location state를 변경하고, 2. 변경된 state 값을 이용하여 API 요청을 보내는 방식이다. 코드는 아래와 같다.

const Home = () => {
    const [location, setLocation] = useState('');
    
    const getFilteredData = async (e) => {
    if (e.target.innerText === '모든 지역') {
      setLocation('');
    } else {
      setLocation(e.target.innerText);
      const result = await Api.get(
        `grounds/?location=${location}&search=${searchValue}`,
      );
      setGroundList({
        isLoading: false,
        length: result.data.length,
        data: result.data.grounds,
      });
    }
    
    return ( ... )
}

결과는 당연히 잘 작동하지 않을거라고 생각했다. 왜냐면 이전에 setState 함수가 비동기적으로 처리되기 때문이다. 그래서 변경된 location이 요청할 url에 제대로 들어가지 않았고 location이 기본 값대로 비어있는 채로 요청을 보내 생각과 다른 결과를 가져왔다.

그래서 비동기로 동작하는 setState 함수와 함께 어떻게 데이터를 요청해야할까 고민 중에.. setState 함수의 두 번째 인자로 callback 함수를 넘길 수 있다는 사실을 알게 됐다. 그리고 콜백 함수는 setState에서 첫 번째 인자가 변경된 뒤에 실행된다.

// example

this.setState({ number: this.state.number + 1 }, () => {
  this.setState({ nextNumber: this.state.number + 1 }, () => {
    this.setState({ nextNextNumber: this.state.nextNumber + 1 }, () => {
      console.log(
        this.state.number,
        this.state.nextNumber,
        this.state.nextNextNumber
      );
    });
  });
});

그렇다면, 콜백함수로 API 요청 함수를 넘기면 되지 않을까? 물론 예제처럼 callback 지옥이 될 수도 있지만, 일단 나는 콜백함수를 하나만 넘길거라서 이 방법으로라도 해보자 하고 시도해봤다.

const getFilteredData = async (e) => {
    if (e.target.innerText === '모든 지역') {
      setLocation('');
    } else {
      setLocation(e.target.innerText, async () => {
        console.log(groundFilter);
        const result = await Api.get(
          `grounds/?location=${location}&search=${searchValue}`,
        );
        setGroundList({
          isLoading: false,
          length: result.data.length,
          data: result.data.grounds,
        });
      });
    }

외않데?

에러 메세지를 읽어보면 useState와 useReducer hook은 2번 째 인자에 콜백 함수를 지원하지 않고, 대신 렌더링 이후 사이드 이펙트를 만들기 위해서는 useEffect 함수를 사용하라고 한다. 알고 보니까, setState 함수에 2번 째 인자를 넘기는건 Class Component에서만 가능하다고 한다 ^^..

어쨌든, useEffect를 통해서 구현해보자.

const getFilteredData = async (e) => {
    if (e.target.innerText === '모든 지역') {
      setLocation('');
    } else {
      setLocation(e.target.innerText);
    }
}

useEffect(() => {
    (async () => {
      const result = await Api.get(
        `grounds/?location=${location}&search=${searchValue}`,
      );
      setGroundList({
        isLoading: false,
        length: result.data.length,
        data: result.data.grounds,
      });
    })();
    console.log('location');
  }, [location]);

사용자가 버튼을 클릭했을 때, state를 변경하고 useEffect 함수에서 변경된 location state를 감지하여 콜백 함수를 실행 시켜서 메인 페이지에 나올 경기장 리스트를 바꾸게끔 했다.

결과는.. 잘 작동한다. 유저가 클릭할 때마다 생각했던 결과를 잘 받아오긴 하는데, 문제가 또 생겼다.

새로고침을 할 때, 윗 부분에 처음 페이지를 구성할 때 useEffect 함수를 사용하는 부분이 있어서 총 2개의 useEffect 함수가 실행되는 것이다. location을 deps로 가지고 있는데 실행이 되는 이유는 (아마) 컴포넌트가 리렌더링 되면서 전체 state가 초기화 되면서 선언되기 때문에 변경을 감지하여 useEffect 함수 2개가 모두 실행되는 것 같다.

useEffect(() => {
    (async () => {
      const result = await Api.get(
        `grounds?location=${location}&search=${searchValue}&offset=${
          (page - 1) * 8
        }`,
      );
      setGroundList({
        isLoading: false,
        length: result.data.length,
        data: result.data.grounds,
      });
    })();
  }, [page]);

요청 1번에 약 0.7초 정도 걸리는 것으로 나오는데 페이지가 리렌더링 될 때마다 요청을 2번 씩 보내는건 성능 상으로 좋지 않을 것 같았고, 꼭 고쳐야 될 문제라고 생각했다.

그래서 어떻게 고칠 수 있을 까 생각해보다 2개의 useEffect 함수가 거의 같은 역할을 하기에 1개의 useEffect 함수에서 page, location 2가지 deps를 넣으면 되지 않을까라는 생각이 들었다. 그래서 location을 deps로 가지고 있던 useEffect 함수는 아예 지워버리고, 하나의 useEffect 함수로 통합해봤다.

useEffect(() => {
    (async () => {
      const result = await Api.get(
        `grounds?location=${location}&search=${searchValue}&offset=${
          (page - 1) * 8
        }`,
      );
      setGroundList({
        isLoading: false,
        length: result.data.length,
        data: result.data.grounds,
      });
    })();
  }, [page, location]);

그 결과, 요청을 1번만 보낸다..!

저렇게 콘솔에 두 번씩 찍히는 이유는, React의 strict mode가 걸려있어서 애플리케이션 내의 잠재적인 에러를 잡기 위해서 2번씩 실행되는거라고 하는데 솔직히 정확하게는 잘 몰라서 이 부분에 대해서 한 번 블로깅을 해봐야겠다. index.js에서 strict mode를 지워보니까 1번씩 잘 실행된다.

https://ko.reactjs.org/docs/strict-mode.html

+ useEffect 함수를 사용하면서 JavaScript의 IIFE도 사용했는데, 이것도 왜 사용했는지 정리해서 글로 써봐야겠다.

https://developer.mozilla.org/ko/docs/Glossary/IIFE


참고

https://velog.io/@dosilv/TIL-React-setState-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-648sv7je

https://velog.io/@seob/React-Hooks%EC%97%90%EC%84%9C-SetState-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0

profile
Front-end Developer

0개의 댓글