[React] 잘못된 useState 사용

Nine·2022년 5월 30일
5

React

목록 보기
16/22

동기

useState는 React에서 가장 많이 쓰이는 Hook이죠.

평소처럼 useState를 잘 쓰다가 우테코 장바구니 미션에서 문제에 직면하게 되었어요.

⚠️ 문제 상황
CheckBox Component 내부의 useState의 초기값을 props로 받은 인자로 초기화를 했더니 체크박스 하나를 클릭해도 존재하는 모든 체크박스가 체크되고 해제되는 에러가 발생했습니다.

export default function CheckBox({
  initialChecked = false,
  }) {
  const [isChecked, setIsChecked] = useState(initialChecked); // 문제 발생!
	...
}

👉 체크박스를 클릭하면 App의 상태가 변하지만 Child 컴포넌트는 변하지 않았어요. 왜 이럴까요?


useState 동작 방식

먼저 useState의 동작 방식에 대해서 살펴봐야겠죠?

  • 아시다피시 useState는 배열을 반환합니다. 우리는 배열 구조 분해 할당을 통해 직접 네이밍을 하고 뽑아서 사용하죠.
const [state, setState] = useState(initiailState)

하지만 setState가 state를 변경시키다고 오해하는 경우가 많아요.


useState 코드를 까보면...

  1. useState같은 Hook들은 react 모듈 안에 선언된 함수예요.

  2. useState는 실행될 때마다 dispatcher를 선언하고 useState 메서드를 실행해서 그 값을 반환해요.

  3. 선언한 dispatcher코드를 살펴보면 전역 변수 ReactCurrentDispatcher로부터 dispatcher를 가져옴을 확인할 수 있어요.

👉 함수가 선언부(dispatcher)보다 상위에 있는 값(ReactCurrentDispatcher)에 접근하고 있죠? 즉, Closure의 개념이 등장하게 됩니다.

👆 useState는 _value 라는 전역에 선언된 변수를 참조하고 있어요. 이것이 바로 우리가 관리하는 ‘상태’이죠!

자신이 선언된 위치에서 접근할 수 있는 _value 상태를 변경하는 것입니다.

  • 위 코드를 다시 자세히 볼까요?
if(_value === undefined)
	_value = initialValue;

👆 보시다시피 최초 호출에만 초기값을 할당하고 이후에는 initialValue가 아예 사용되지 않죠?

자 그럼 길게 흐름을 봅시다.

  1. 처음 컴포넌트 함수가 실행되어 useState를 실행합니다. 👉 _value가 undefined이니 initialValue를 할당합니다.

  2. 다시 컴포넌트 함수가 실행되면 useState가 실행됩니다. 👉 이 때에는_value가 undefined가 아니죠. 👉 useState가 반환한 값을 변수에 할당합니다.

  3. setState함수는 자신과 함께 반환된 변수를 변경시키는 것이 아니라 다음 useState가 반환할 react 모듈의 _value를 변경시키고 컴포넌트를 리렌더링 시키는(트리거) 역할을 합니다.

결론

변경된 값은 다음 컴포넌트 함수가 실행될 때 useState가 가져오기 때문에

setState 호출 이후 로직에서도 state의 값은 이전과 동일합니다. 

Q: 최신의 state는 누가 가져오나요?
A: useState가 state를 가져옵니다.

Q: state는 누가 변경시키나요?
A: state를 변경한다기보다는 _value값을 변경하는거죠. setState가 _value를 변경하고 이를 배열 구조 분해 할당으로 가져와서 사용하는 것입니다.

해결 방법

  • useState를 코드에서 정의하면 default로 정의한 값은 절대 바뀌지 않습니다. useState는 딱 한 번만 실행되기 때문이죠.
  • 따라서 props가 아무리 변해도 state가 바뀔 수 없는 것입니다.

❗해결방법은 다음과 같습니다.

export default function CheckBox({
  initialChecked = false,
}) {
  const [isChecked, setIsChecked] = useState(false); // 초기값을 props로 설정하면 안됩니다.

  useEffect(() => {
    setIsChecked(initialChecked);
  }, [initialChecked]); // 이렇게 useEffect를 통해 props가 바뀔때마다 setState를 해줍니다.

👆 이렇게 initialChecked를 useState의 인자로 넘기지 않아야 변화하는 initialChecked라는 값을 지속적으로 추적할 수 있겠죠.


참고 자료

Do not use props as default value of React.useState() directly | by Allen Wei | Medium

react useState에 대한 공통적인 실수 (tistory.com)

profile
함께 웃어야 행복한 개발자 장호영입니다😃

3개의 댓글

comment-user-thumbnail
2022년 9월 4일

안녕하세요. 좋은 글 감사합니다 :)
useState의 lazy initialization 기능에 대해 공부하면서 useState 코드를 까보다 보니 이해가 안가는 부분이 많아서 글을 타고타고 넘어와 Nine님의 글을 읽게 되었습니다 🤓

실례가 되지 않는다면 'useState를 까보면...' 에서 보여주신 코드의 출처가 어떻게 되는지 알려주실 수 있을까요??
facebook/react github에서 열심히 찾아보면서 공부중인데 해당 코드를 못찾겠어서,, 도움을 받을 수 있다면 답변 한번 부탁드립니다!

1개의 답글