React는 단방향 데이터 흐름에 따라, 상태(state)와 같은 데이터가 항상 상위 컴포넌트에서 하위 컴포넌트로 흐른다.
그렇다면, 하위 컴포넌트에서 상위 컴포넌트의 상태(state)를 변경할 수 있는 방법은 없을까?
여기서 사용할 수 있는 방법이 상태 끌어올리기(Lifting State Up)다.
상태 끌어올리기(Lifting State Up)는 하위 컴포넌트에서 상위 컴포넌트의 상태를 변경하는 것을 말한다. 상위 컴포넌트의 상태를 변경하는 함수를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트에서 실행하게 되면 상위 컴포넌트의 상태를 변경할 수 있다.
// Cart.tsx
function Cart() {
const { carts, deleteCartItem, isEmpty } = useCart();
const [checkedItems, setChekedItems] = useState<number[]>([]);
const handleCheckItem = (id: number) => {
if (checkedItems.includes(id)) {
// 언체크
setChekedItems(checkedItems.filter((item) => item !== id));
} else {
// 체크
setChekedItems([...checkedItems, id]);
}
};
return (
<>
<div className="content">
{carts.map((item) => (
<CartItem
key={item.id}
cart={item}
checkedItems={checkedItems}
onCheck={handleItemDelete}
/>
))}
</div>
...
</>
);
}
checkedItems
상태, 그리고 상태를 수정하는 handleCheckItem
함수 객체를 onCheck
에 담아CartItem
이라는 하위 컴포넌트로 전달하였다.// CartItem.tsx
interface Props {
cart: Cart;
checkedItems: number[];
onCheck: (id: number) => void;
}
function CartItem({cart, checedItem, onCheck }: Props) {
const isChecked = useMemo(() => {
return checkedItems.includes(cart.id);
}, [checkedItems, cart.id]);
const handleCheck = () => {
onCheck(cart.id);
};
return (
<div className='check'>
<div>
<CheckIconButton
isChecked={isChecked}
onCheck={handleCheck}
/>
</div>
</div>
...
);
}
cart.id
가 존재하는지 존재하지 않는지 반복해야하기 때문에, useMemo를 사용해 이전 값을 기록하도록 하고, checkedItems
와 cart.id
가 변경되면 다시 계산하도록 설정한다.onCheck
에 cart.id
를 인자로 넣고, 하위 컴포넌트인 CheckIconButton
에 Props로 전달한다.// CheckIconButton.tsx
interface Props {
isChecked: boolean;
onCheck: () => void;
}
function CheckIconButton({ isChecked, onCheck }: Props) {
return (
<CheckIconButtonStyle onClick={onCheck}>
{isChecked ? <FaRegCheckCircle /> : <FaRegCircle />}
</CheckIconButtonStyle>
);
}
onCheck
함수를 호출하여, checkedItems
의 상태에 cart.id
가 존재한다면, 상태를 변경한다.위와 같은 방식으로 하위 컴포넌트에서 상위 컴포넌트의 상태를 변경할 수 있다. 즉, 상위 컴포넌트에서 전달받은 상태 변경 함수를 하위 컴포넌트의 특정한 이벤트가 발생했을 때, 호출하여 상태를 변경할 수 있다.