본문 바로가기

FrontEnd

useState의 함정 : useEffect와 사용 시 주의할 점.

반응형

useState를 너무 많이 사용하지 않기

단일 진실 원천 - 파생된 상태를 저장하기 위한 상태 피하기

상태란 무엇인가요? 공식문서에 따르면

각 데이터 조각에 대해 세 가지 질문을 합니다.
1) props를 통해 부모로부터 전달됩니까? 그렇다면 아마도 상태가 아닐 것입니다.
2) 시간이 지나도 변함없나요? 그렇다면 아마도 상태가 아닐 것입니다.
3) 컴포넌트의 다른 상태 또는 prop을 기반으로 계산할 수 있습니까? 그렇다면 상태가 아닙니다.

3번 케이스에서 주의할 점 : 비동기 데이터를 이용한 파생 상태를 state에 저장하지 않습니다.

useEffect는 상태를 React 외부의 요소(서버, 브라우저 스토리지 등)와 동기화 하는 경우에만 사용합니다.

import { fetchData } from './api'
import { computeCategories, getMoreCategories } from './utils'

const App = () => {
  const [data, setData] = React.useState(null)
  const [categories, setCategories] = React.useState([])

  React.useEffect(() => {
    async function fetch() {
      const response = await fetchData()
      setData(response.data)
    }

    fetch()
  }, [])

  React.useEffect(() => {
    if (data) {
      setCategories(computeCategories(data))
    }
  }, [data])

  return (
    <>
      ...
      <Button onClick={() => setCategories(getMoreCategories())}>
        Get more
      </Button>
    </>
  )
}
"카테고리"가 무엇인지 예측할 수 있는 방법이 없습니다. 비동기 데이터인가요? 사용자 인터랙션 결과를 저장하는 상태인가요?
  • 페이지 로드 시 카테고리는 X 입니다. (fetch)
  • 사용자가 버튼을 클릭하면 카테고리는 Y입니다.
  • 탭에 초점을 맞추거나 네트워크에 다시 연결할 때 자동 다시 가져오기와 같은 기능이 있는 react-query를 사용하고 있기 때문에, 데이터 가져오기가 다시 실행되면 ), 범주는 다시 X가 됩니다.
실수로 가끔씩만 발생하는 추적하기 어려운 버그를 도입했습니다.
즉, useEffect 내에서 비동기 데이터를 이용해 동기적으로 데이터를 파생하는 state를 제거하세요.
import { fetchData } from './api'
import { computeCategories } from './utils'

const App = () => {
  const [data, setData] = React.useState(null)
-  const [categories, setCategories] = React.useState([])
+  const categories = data ? computeCategories(data) : [] // 비동기 이용 파생

  React.useEffect(() => {
    async function fetch() {
      const response = await fetchData()
        setData(response.data) // 비동기적 사용
      }

      fetch()
    }, [])

-  React.useEffect(() => {
-    if (data) {
-      setCategories(computeCategories(data)) // 동기적 파생 상태 사용
-    }
-  }, [data])

  return <>...</>
}

Props를 상태에 넣기

props로 전달된 값으로 상태를 초기화할 시, 다시 마운트 되는게 아니면, 이후에 전달되는 props와 값이 달라질 수 있습니다.

 

보통 아래의 세 가지 방법으로 해결하며, 완벽한 해답은 없습니다.

 

dumb component를 만드는 상태 끌어올리기 패턴이 좋은 방법이긴 하지만,

대규모 애플리케이션일 경우 항상 이렇게 구현하기 쉽지 않습니다.

 

  • 상태 끌어올리기 (의존성 주입)
    • 해당 상태 관리를 부모에게 맞김
  • key prop으로 초기화 할 상태 전달 (안정적인 키)
    • key가 바뀌면 리액트는 같은 위치 같은 태그여도 다시 마운트함
  • 모달 (조건부 렌더링)
    • 플래그에 의해 dom이 다시 마운트 되면 상태 값이 다시 초기화됩니다.

아래 패턴은 주의해야 합니다.

useEffect로 변화감지 동기화 > 안티 패턴

 

참고

자세한 코드는 아래에서 확인할 수 있습니다.

반응형