React는 데이터가 부모에서 자식으로만 흐르는 단방향 데이터 흐름(One-way Data Flow)을 따른다.
이 구조는 데이터의 흐름을 예측하기 쉽게 만들어 주지만, 반대로 자식 컴포넌트에서 발생한 이벤트로 다른 형제 컴포넌트나 부모의 UI를 변경해야 할 때 제약이 생긴다.
이를 해결하기 위한 React의 기본 설계 패턴이 State 끌어올리기(Lifting State Up)이다.
props를 통해 전달된다. 버그가 발생했을 때 데이터가 어디서 변경되었는지 추적하기 쉽고 컴포넌트 간 결합도가 낮아진다.props가 부모의 상태값(state)인지 단순 하드코딩된 값인지 알 수 없으며, 자식이 부모의 상태를 직접 수정할 수도 없다.만약 두 개 이상의 자식 컴포넌트가 동일한 데이터를 공유하거나 동기화해야 한다면, 데이터의 소유권을 어디에 두어야 할지 문제가 발생한다.
동일한 상태를 공유해야 하는 여러 컴포넌트가 있다면, 그 상태를 공통의 가장 가까운 부모 컴포넌트로 끌어올려 관리하는 패턴이다.
부모 컴포넌트는 상태값(state)과 함께 상태를 변경하는 상태 변경 함수(setState)를 자식에게 props로 전달한다. 자식 컴포넌트는 이벤트 발생 시 전달받은 함수를 호출하여 부모의 상태를 변경하도록 요청(역방향 통신)한다.

// ChildA.jsx (상태를 화면에 보여주는 컴포넌트)
export function CounterViewer({ count }) {
return <div>현재 카운트: {count}</div>;
}
// ChildB.jsx (상태 변경 이벤트를 발생시키는 컴포넌트)
export function CounterButton({ onIncrease }) {
return <button onClick={onIncrease}>1 증가</button>;
}
// Parent.jsx
import { useState } from "react";
import { CounterViewer } from "./ChildA";
import { CounterButton } from "./ChildB";
export default function Parent() {
// 공통으로 공유할 상태를 부모에서 선언
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount((prev) => prev + 1);
};
return (
<div>
{/* 한 자식에게는 상태값을 읽기 전용으로 전달 */}
<CounterViewer count={count} />
{/* 다른 자식에게는 상태를 변경할 수 있는 함수를 전달 */}
<CounterButton onIncrease={handleIncrease} />
</div>
);
}
State 끌어올리기는 컴포넌트 계층이 얕을 때는 직관적이고 깔끔하다.
하지만 컴포넌트 트리가 깊어지면 상태와 핸들러 함수를 중간 컴포넌트들이 단순히 전달만 하는 Props Drilling 문제가 발생한다.
이러한 깊은 구조에서는 상태 끌어올리기 대신 Context API나 전역 상태 관리 라이브러리를 도입해 상태를 전역 저장소에서 직접 주입받는 방식을 고려해야 한다.