useState는 내부 상태를 위한 훅이다 — 다시 마주한 기본 원리

2025-05-14

  • useState내부 상태를 위한 훅이다.
  • 초기값은 최초 렌더링에서만 사용된다.
  • 외부에서 전달된 props가 바뀌어도 useState 값은 자동으로 바뀌지 않는다.
  • 외부 값을 반영하고 싶다면 useEffect로 따로 감지해서 수동으로 동기화해야 한다.

리액트를 배우는 초반에 useState는 내부 상태를 위한 훅이라는 사실을 자연스럽게 받아들였고, 이후로도 당연한 전제로 개발을 이어왔습니다. 그러다 이 동작 방식을 의도적으로 활용해야 하는 상황을 직접 마주하고 나서야, React가 이렇게 동작하도록 만든 이유와 구조적 의도가 더 분명하게 이해되었습니다.


🤔 props가 바뀌었는데 상태는 그대로?

예제를 하나 보겠습니다.

import { useState } from "react";

export function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>부모 값 증가</button>
      <Child initialValue={count} />
    </>
  );
}

function Child({ initialValue }: { initialValue: number }) {
  const [value, setValue] = useState(initialValue);

  return (
    <>
      <p>부모에서 받은 값: {initialValue}</p>
      <p>내부 상태값: {value}</p>
      <button onClick={() => setValue((v) => v + 1)}>내 상태 증가</button>
    </>
  );
}

이 코드에서 Parent의 count 값이 바뀌면 initialValue도 바뀌겠지만,
자식 컴포넌트의 useState(initialValue)로 관리되는 value는 바뀌지 않습니다.
왜냐하면 useState는 컴포넌트가 다시 렌더링되더라도 초기값을 다시 적용하지 않기 때문입니다.


💡 그렇다면 외부 값으로 상태를 다시 초기화하려면?

방법은 두 가지입니다:

1. useEffect로 직접 감지해서 반영하기

useEffect(() => {
  setValue(initialValue);
}, [initialValue]);

이 방식은 명시적이고 예측 가능하지만, 상황이 반복되거나 로직이 많아질수록 코드가 점점 복잡해질 수 있습니다.

2. key를 이용해 컴포넌트를 강제로 재마운트

<Child key={count} initialValue={count} />

React는 key가 바뀌면 해당 컴포넌트를 완전히 unmount → remount 합니다.
이렇게 하면 useState(initialValue)새로운 초기값으로 다시 실행됩니다.


왜 이 기초적인 개념을 다시 꺼냈을까? - useState와 useOptimistic

useState는 한 번 초기값을 주면, 이후 외부에서 값이 바뀌어도 내부 상태에는 아무 영향이 없습니다. 이 동작 방식에 너무 익숙해져 있다 보니, useOptimistic을 마주했을 때 자연스럽게 같은 원리로 이해하려 했습니다. 하지만 그건 오해였습니다.

useOptimistic은 겉보기에는 상태처럼 보이지만, 실제로는 외부에서 전달된 값을 따라 움직입니다. 특히 pending 상태가 아닐 때는, 단순히 첫 번째 인자로 전달된 값을 그대로 반환하죠. 이처럼 외부 값에 동기화되는 흐름은 기존의 상태 관리 방식과는 다른 결을 가지고 있었고, 오히려 그 낯섦이 React의 상태 처리 방식을 다시 돌아보게 만들었습니다.

다음 포스팅에서는 사이드 프로젝트 Pick Road 를 진행하며, useOptimistic을 실제로 어떻게 활용했는지 구체적인 사례를 중심으로 풀어보려 합니다.