useState 업데이트 이슈 해결하면서 겪은 삽질

김방울·2022년 11월 10일
0

Issue & Memoirs🐱

목록 보기
1/2

Issue

프로젝트 중 다음과 같은 계산기 기능을 구현하게 되었습니다.
다른 state에 의존하여 계산되는 state 값을 사용하면서, 값이 바로 업데이트되지 않아 잘못된 계산 결과가 출력되는 이슈가 있었습니다.

수정 전 코드

슬라이더는 react-range 라이브러리를 사용하여 구현하였고,
대략적인 코드는 다음과 같았습니다.

const MainSection = () => { 
  // PARAM state
  const [buyingPrice, setBuyingPrice] = useState([5000]); // 매수할 가격!
  const [expectedIncome, setExpectedIncome] = useState(() => getExpectedIncome(buyingPrice[0])); // 예상 배당수익
  const [step, setStep] = useState(10);

  // PARAM 매수가격 데이터
  const buyingPriceInfo = {
    min: 5000,
    max: 20000000,
  };
  
  // FUNCTION slider 변경 시 실행할 함수
  const onChangeSlider = (value) => {
    setBuyingPrice(value);
    setExpectedIncome(getExpectedIncome(buyingPrice[0]));
    return;
  }
  
    // FUNCTION 예상 연배당수익 구하는 함수
  const getExpectedIncome = (value) => {
    const result = (value * data['직전 연환산배당률']) / 1000 / 100;
    return result;
  }
  ...
  
  return (
    ...
    <Range
      step={step}
      min={buyingPriceInfo.min}
      max={buyingPriceInfo.max}
      values={buyingPrice}
      onChange={onChangeSlider}
      renderTrack={({ props, children }) => (
        <div className='TransactionDetail__main-slider-track' {...props}
          style={{
            background: getTrackBackground({
              values: buyingPrice,
              colors: ['#3776EF', '#ccc'],
              min: buyingPriceInfo.min,
              max: buyingPriceInfo.max,
            })
          }}
        >
          {children}
        </div>
      )}
      renderThumb={({ props }) => (
        <div className='TransactionDetail__main-slider-thumb' {...props} />
      )}
      ...
    />
  )

지금 다시 보니 <Range /> 컴포넌트에 있는 옵션들을 객체로 따로 분리했으면 더 보기 편한 코드가 되지 않았을까... 싶네요 🙃

// FUNCTION slider 변경 시 실행할 함수
  const onChangeSlider = (value) => {
    setBuyingPrice(value);
    setExpectedIncome(getExpectedIncome(buyingPrice[0]));
    return;
  }

다음과 같이 expectedIncome 의 값이 buyingPrice의 값에 의존하고 있었습니다.

원인

useState를 이용한 상태 업데이트는 비동기적이기 때문에 변경 사항이 즉시 반영되지 않습니다. 컴포넌트를 즉각적으로 갱신하지 않고 변경사항을 대기열에 집어넣어 여러 변경사항과 함께 일괄적으로 갱신합니다. 이를 위해 컴포넌트의 렌더링을 뒤로 미룰 수 있고, setState를 여러 번 호출한다고 해도 한 번의 렌더링만 이루어질 수 있습니다.

Solve Process

삽질 1 - setState 인자로 익명 함수 넘겨주기

과거에 setState 의 인자로 익명 함수를 넘겨 주는 방법을 활용해서 다른 state 값에 의존하는 state의 미스매치를 막을 수 있다! 라는 내용을 강의에서 들었던 게 기억나 적용해 보았습니다.

  // FUNCTION slider 변경 시
  const onChangeSlider = (value) => {
    setBuyingPrice((prev) => value);
    setExpectedIncome((prev) => getExpectedIncome(buyingPrice[0]));
    return;
  }

결과는... 장렬히 실패😱
setState 의 인자로 익명 함수를 넘겨서 state 미스매치를 해결하는 방법은 이전 값에 의존할 때 사용이 가능한 방법이었습니다. (ex: 카운터)

삽질 2 - useEffect() 사용

  // FUNCTION slider 변경 시
  const onChangeSlider = (value) => {
    setBuyingPrice(value);
    return;
  }

  const updateExpectedIncome = useEffect(() => {
    console.log('update');
    setExpectedIncome(getExpectedIncome(buyingPrice[0]));
    return;
  }, [buyingPrice]);

첫 삽질 실패 후, 복잡하게 생각하지 말고 단순하게 생각해보자! 라는 생각이 들어
의존성 배열로 buyingPrice 를 갖는 useEffect 훅을 만들었고 결과는 잘 동작하였습니다.

그런데 onChange 이벤트가 일어나지 않은 초기 렌더링 시에도 useEffect 훅이 실행되는 것이 마음에 들지 않았습니다. 추가렌더링은 지양...해야지....😢
useEffectonChangeSlider 훅 밖에 써야 하기 때문에, 더 비직관적으로 느껴지기도 했습니다.

최종 적용 - 다른 State에 의존하지 않기

const onChangeSlider = (value) => {
    setBuyingPrice(value);
    setExpectedIncome(getExpectedIncome(value));
    return;
  }

코드롤 다시 보니 buyingPrice state에 무조건 의존해야 할 필요가 없음을 깨달았습니다. 어차피 buyingPrice state는 매개변수로 받아오는 value의 값으로 업데이트 될 것이고, setExpectedIncome() 훅에도 동일하게 value 를 전달해주면 되는 것이었습니다.
이런 간단한 해결방법을 두고 삽질을 했다니..............!!!🤦

회고

  • 때로는 화려한 기술이나 방법을 생각하는 것보다, 간단하고 깔끔한 방법이 더 좋은 솔루션일 수도 있겠다는 생각이 들었습니다.
  • 최종 적용을 완료한 코드도 다시 보니 개선할 점이 생기는 것 같습니다. 수시로라도 짰던 코드들을 다시 점검하는 시간을 가져봐야겠습니다. 팀원들과 다같이 코드리뷰를 하고 리팩토링을 하면 참 좋을 것 같은데... 어른의 사정(이라고 쓰고 일정 문제라고 읽습니다😂) 때문에 당분간은 힘들 것 같으니 혼자서라도 수시로 점검해봐야겠습니다.
profile
코딩하는 고양이🐱 / UI Developer, Front-end Developer

0개의 댓글