준영이의 성장일기(FrontEnd)

React useEffect, 상태 업데이트의 비동기성 본문

프론트엔드/React.js

React useEffect, 상태 업데이트의 비동기성

Jay_Jung 2024. 8. 17. 00:04

 

프로젝트 진행 과정에서 태스크는 다음과 같았다. 가게의 메뉴들을 할인하고자 할인 할 가격을 입력하고 할인 적용을 위해 체크박스를 클릭한다. 이후 전체 메뉴에 동일한 할인가격을 입력한 후 다음 페이지로 넘어가면 해당 문구가 렌더링 되도록 하는 것이 목포였다. 하지만 해당 과정에서 발생한 이슈가 있는데 이슈는 다음과 같았다.

 

❗이슈

1. 기존의 로직은 모든 메뉴에 3천원을 입력 한 후 모든 메뉴가 동일한 가격인지 그리고 체크가 되어 있는지 검사 후 모두 만족한다면 Set을 통해 중복을 제거했다. 그럼 결국엔 3천원만 있는 해당 배열의 길이는 1개가 될테고 이후 setIsUniformPrice를 통해 isUniform 상태를 true로 업데이트 하였다. 하지만 이후 바로 상태를 읽어들일 때 업데이트된 값을 반영하지 못했고 다음 페이지로 있을 때 ~점 전 메뉴 ~원 할인 이라는 문구의 렌더링 또한 발생하지 않았다.

 

<기존에 작성했던 로직>

const activeDiscounts = currentEvent.discounts.filter((d) => d.isChecked)
const allPrices = activeDiscounts.map((d) => d.discountPrice)
const uniquePrices = [...new Set(allPrices)]
const uniformPrice = uniquePrices.length === 1 && uniquePrices[0] > 0
console.log(uniformPrice)
setIsUniformPrice(uniformPrice)

 

 

 

 

❗문제 원인

문제가 발생한 이유는 상태 업데이트가 비동기적으로 이루어지기 때문에, setDiscountChecked 함수가 완료된 후에도 바로 상태를 읽어들일 때 업데이트된 값을 반영하지 못했다. 즉 이전에 setDiscountChecked나 setDiscountPrice를 호출한 직후에 상태를 확인하려고 했기 때문에, 실제로 상태가 반영되기 전에 상태를 읽으려는 문제가 발생했다.

 

setDiscountChecked와 setDiscountPrice는 할인하고자 하는 메뉴에 해당하는 체크박스의 체크 유무, 그리고 할인가격을 스토어에 저장하는 함수이다.

 

❗해결

이를 해결하기 위해, useEffect를 활용하여 currentEvent.discounts의 상태가 변경될 때마다 isUniformPrice를 재계산하도록 하는 방법을 생각했다.이렇게 되면 currentEvent.discounts의 상태가 변경될  때 마다 즉각적으로 모든 가격이 동일한지 판단 후 모든 가격이 동일하다면 isUniformPrice를 true로 업데이트 한다. 

 

setDiscountChecked와 setDiscountPrice를 통해 currentEvent.discounts에 있는 isChecked와 discountPrice 속성이 변하게 되고 해당 속성의 변화를 useEffect가 인지하도록 구현한 것이다.

 

 

해당 문제를 해결하면서 관련된 개념을 찾아봤고 관련된 개념은 useState의 비동기적인 특성이다.

 

상태 업데이트의 비동기성  

1. React의 setState (혹은 setDiscountChecked, setDiscountPrice 같은 상태 변경 함수)는 비동기적으로 작동한다. 즉, 상태를 변경하는 함수가 호출된 후, 상태가 즉시 업데이트되지 않고, 리렌더링이 발생한 후에야 상태가 반영된다. 

-> 이전에 setDiscountChecked나 setDiscountPrice를 호출한 직후에 상태를 확인하려고 했기 때문에, 실제로 상태가 반영되기 전에 상태를 읽으려고 한 것이다.

 

-> 비동기적으로 작동하는 이유는 다음과 같다.

리액트는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한번에 처리한다. 

 

2.  업데이트 된 state를 바로 확인할 수 있는 방법

 

업데이트된 상태값을 바로 보려면 useEffect, deps를 사용해서 state가 업데이트될 때 변경된 부분을 확인할 수 있다. useEffect는 상태가 변경된 이후에 동작하고 이에 근거하여 구현한 코드는 다음과 같다.

useEffect(() => {
const allPrices = currentEvent.discounts.map((d) =>
  d.isChecked ? d.discountPrice : 0,
)
const uniquePrices = [...new Set(allPrices)]
const uniformPrice = uniquePrices.length === 1 && uniquePrices[0] > 0
setIsUniformPrice(uniformPrice)
}, [currentEvent.discounts])

 

컴포넌트가 처음 렌더링될 때, useEffect는 아직 실행되지 않는다. 초기 렌더링에서는 currentEvent.discounts에 대해 설정된 초기 상태값이 화면에 반영된다. 만약 currentEvent.discounts가 빈 배열이거나 초기 상태라면, 이 값들이 컴포넌트에 그대로 렌더링된다.

 

✅ currentEvent.discounts가 변경되면, 컴포넌트를 리렌더링 한다. 리렌더링 이후에 useEffect가 발동하며 useEffect의 의존값으로 currentEvent.discounts로 설정했기 때문에 currentEvent.discounts의 변경 유무에 따라서 다시 리렌더링이 발생한다. 

 

setIsUniformPrice가 호출되면, isUniformPrice 상태가 업데이트되었음을 감지하고 이를 감지하여 전 메뉴 ~원 할인이라는 문구를 렌더링 한다.

 

 

앞으로의 목표 

오늘 블로그 이후에도 리액트 공식문서를 일주일에 두번 정도 정리하는 시간을 가져야겠다.