티스토리 뷰

문제 상황

  • material-ui의 drawer 컴포넌트를 사용하고 있음
  • 부모 컴포넌트(검색 결과를 리스트로 보여주는 페이지)가 자식 컴포넌트인 drawer에게 state(인원 수)를 넘겨줌.
  • drawer는 부모 컴포넌트로부터 받은 인원 수로 state를 초기화하고, drawer내에서 인원 수를 변동시킬 수 있음.
  • drawer 내에서 인원 수를 변동시킨 다음 저장 버튼을 누르면 drawer가 닫히면서 바뀐 인원 수로 검색 api 재호출하여 그 결과를 부모 컴포넌트인 리스트 페이지에 렌더링함.
  • 그런데 문제는 api call이 백그라운드에서 진행되고 있을 때, drawer를 열어서 인원 수를 조작하고 있는 상황에서, api 호출이 끝나고 리스트가 렌더링되면, drawer에서 조작하고 있던 인원 수가 다시 초기화 되는 것이었다.
    • 예를 들어 맨 처음에 인원 수 2명으로 검색 -> api call이 진행되며 리스트 페이지는 로딩 중 -> 이 와중 인원 수 변경 drawer를 열음 -> 이 drawer는 인원 수 2로 초기화되어있는데 인원 수 3으로 조작함 -> 이 상태에서 저장을 누르지 않고 drawer가 열려있을 때 뒤에서 돌고 있던 api call이 끝나면 내가 만지지 않았는데 drawer에 표시되는 인원 수가 다시 2로 변함.

원인

drawer 컴포넌트가 부모로부터 전달받고 있는 인원 수 데이터는 Map 타입이고, drawer 컴포넌트 내에 이런 코드가 있었다.

useEffect(() => {
    if (drawerIsOpen) {
        setDrawerPassengerNumbers(passengerNumbersFromParent);
    }
}, [drawerIsOpen, passengerNumbersFromParent]);

여기서 passengerNumbersFromParent 는 부모로부터 전달 받은 인원 수이고, setDrawerPassengerNumbers 은 drawer 내부적으로 갖고 있는 인원 수 state이다. drawer가 열릴 때는 항상 부모로부터 전달받은 인원 수를 업데이트하여 보여주도록 한 것이다. 그런데 여기서 passengerNumbersFromParent 가 Map 타입이었던 것이 원인이다. useEffect의 dependency array에 primitive value가 아닌 object 타입이 있다면 주의해서 봐야한다. 만약 key-value가 동일하다고 하더라도 useEffect는 refrential equality 를 보기 때문에, 리렌더링으로 인해 object가 새로 만들어졌다면 내부 값이 동일해도 useEffect는 한 번 더 실행된다.

이 경우 부모 컴포넌트에서 api call이 끝나고 그 검색 결과를 보여주기 위해 리렌더링 되면서, passengerNumbersFromParent 가 다시 만들어져 자식 컴포넌트에게 전달된 것이다. 자식 컴포넌트에 있는 UseEffect는 이것이 새로 만들어진 것이므로 reference가 달라져서 diff가 있다고 생각하고 다시 한번 실행하여, drawer 내부의 local state가 다시 부모 컴포넌트의 인원 수로 초기화된 것이다.

해결 방법 - 1

useEffect 코드는 유지하되, 부모 컴포넌트에서 passengerNumbersFromParent 를 useMemo로 감싼 뒤 자식에게 전달하는 방법이 있다. useMemo로 감싸면 refrential equality가 유지되므로, useEffect는 diff가 없다고 판단할 것이다. 다만, 이 방법은 expensive한 연산도 아닌데 referential equality만을 위해서 사용하는 것이므로 useMemo의 원래 취지와 맞지 않는다는 의견이 있다.

해결방법 - 2

Material ui의 drawer 컴포넌트의 자식 컴포넌트들(즉 drawer의 컨텐츠가 되는 컴포넌트들)을 따로 컴포넌트로 분리하여, 자식 컴포넌트(=컨텐츠 컴포넌트)가 상태를 갖고 있도록 하는 방법이 있다. 이렇게 하면 UseEffect를 쓰지 않을 수 있기 때문이다.

material ui의 drawer 컴포넌트는 드로워가 닫혀 유저 눈에 보이지 않아도 여전히 마운트된 상태로 유지된다. 애니메이션 등의 이유 때문이라고 한다. 그리고 부모로부터 전달받은 state를, 자식이 자신의 local state 로 initialize한 경우에는, 원래는 부모의 state가 바뀌어도 자식은 자동으로 그 state를 업데이트하진 않는다.

즉 결론은 drawer가 닫힐 때 내부 컨텐츠 컴포넌트를 언마운트시킬 수 있으면, drawer가 다시 열릴 때에 컨텐츠 컴포넌트가 다시 마운트되면서 이때 부모로부터 전달받은 state로 다시 초기화를 할 것이라는 얘기다. useEffect를 쓸 필요 없이. 그래서 drawer가 원래 갖고 있었던 state를 컨텐츠 컴포넌트가 갖고 있도록 분리하면 문제가 해결된다.

Ref

https://www.benmvp.com/blog/object-array-dependencies-react-useEffect-hook/

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함