프로젝트 중 다음과 같은 계산기 기능을 구현하게 되었습니다.
다른 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를 여러 번 호출한다고 해도 한 번의 렌더링만 이루어질 수 있습니다.
과거에 setState
의 인자로 익명 함수를 넘겨 주는 방법을 활용해서 다른 state 값에 의존하는 state의 미스매치를 막을 수 있다! 라는 내용을 강의에서 들었던 게 기억나 적용해 보았습니다.
// FUNCTION slider 변경 시
const onChangeSlider = (value) => {
setBuyingPrice((prev) => value);
setExpectedIncome((prev) => getExpectedIncome(buyingPrice[0]));
return;
}
결과는... 장렬히 실패😱
setState
의 인자로 익명 함수를 넘겨서 state 미스매치를 해결하는 방법은 이전 값에 의존할 때 사용이 가능한 방법이었습니다. (ex: 카운터)
// FUNCTION slider 변경 시
const onChangeSlider = (value) => {
setBuyingPrice(value);
return;
}
const updateExpectedIncome = useEffect(() => {
console.log('update');
setExpectedIncome(getExpectedIncome(buyingPrice[0]));
return;
}, [buyingPrice]);
첫 삽질 실패 후, 복잡하게 생각하지 말고 단순하게 생각해보자! 라는 생각이 들어
의존성 배열로 buyingPrice
를 갖는 useEffect
훅을 만들었고 결과는 잘 동작하였습니다.
그런데 onChange
이벤트가 일어나지 않은 초기 렌더링 시에도 useEffect
훅이 실행되는 것이 마음에 들지 않았습니다. 추가렌더링은 지양...해야지....😢
또 useEffect
는 onChangeSlider
훅 밖에 써야 하기 때문에, 더 비직관적으로 느껴지기도 했습니다.
const onChangeSlider = (value) => {
setBuyingPrice(value);
setExpectedIncome(getExpectedIncome(value));
return;
}
코드롤 다시 보니 buyingPrice
state에 무조건 의존해야 할 필요가 없음을 깨달았습니다. 어차피 buyingPrice
state는 매개변수로 받아오는 value
의 값으로 업데이트 될 것이고, setExpectedIncome()
훅에도 동일하게 value
를 전달해주면 되는 것이었습니다.
이런 간단한 해결방법을 두고 삽질을 했다니..............!!!🤦