인강 & 책 스터디 노트/Udemy 리액트 완벽 가이드 2024

React에서 useState로 상태 업데이트시 콜백 함수 방식을 사용해야 하는 이유

thisisamrd 2024. 8. 25.

늘상 사용하던 코드에 대해 의문을 갖지 않다가 인강을 들으며 왜 이렇게 사용해야 하는지 이해하고 있습니다. 오늘은 리액트에서 useState를 사용하여 상태 업데이트를 할 때 왜 콜백함수인 (prevUserInput) => { ... } 와 같은 형태로 써야 하는지에 대해 포스팅 하겠습니다.

 

 

 

 

우선 오늘 배운 코드는 아래와 같습니다. 

import { useState } from "react";

export default function UserInput() {
  const [userInput, setUserInput] = useState({
    initialInvestment: 10000,
    annulInvestment: 1200,
    expectedReturn: 6,
    duration: 10,
  });

  function handleChange(inputIdentifier, newValue) {
    setUserInput((prevUserInput) => {
      return {
        ...prevUserInput,
        [inputIdentifier]: newValue,
      };
    });
  }

  return (
    <section id="user-input">
      <div className="input-group">
        <p>
          <label>Initial Investment</label>
          <input
            type="number"
            required
            value={userInput.initialInvestment}
            onChange={(event) =>
              handleChange("initialInvestment", event.target.value)
            }
          />
        </p>
        <p>
          <label>Annual Investment</label>
          <input
            type="number"
            required
            value={userInput.annulInvestment}
            onChange={(event) =>
              handleChange("annulInvestment", event.target.value)
            }
          />
        </p>
      </div>
      <div className="input-group">
        <p>
          <label>Expected Return</label>
          <input
            type="number"
            required
            value={userInput.expectedReturn}
            onChange={(event) =>
              handleChange("expectedReturn", event.target.value)
            }
          />
        </p>
        <p>
          <label>Duration</label>
          <input
            type="number"
            required
            value={userInput.duration}
            onChange={(event) => handleChange("duration", event.target.value)}
          />
        </p>
      </div>
    </section>
  );
}

 

 

이 중 아래와 같이 onChange시 useState를 업데이트 할 때 왜 아래와 같은 방식(prevUserInput 사용 부분)으로 써야만 하는지에 대해 알아보겠습니다.

  function handleChange(inputIdentifier, newValue) {
    setUserInput((prevUserInput) => {
      return {
        ...prevUserInput,
        [inputIdentifier]: newValue,
      };
    });
  }

 

 

결론부터 말하자면, 위에서 (prevUserInput)을 사용하는 이유는 React의 useState에서 상태를 업데이트할 때 이전 상태 값을 안전하게 참조하기 위해서입니다. 

 

 

 

 

 

 

prevState를 사용하지 않는 경우

만약 prevState를 사용하지 않고 업데이트 하면 어떻게 될까요?

 

function handleChange(inputIdentifier, newValue) {
  setUserInput({
    ...userInput,
    [inputIdentifier]: newValue,
  });
}

 

 

위 코드에서 setUserInput은 이전 상태(userInput)를 직접 참조하여 새로운 상태를 설정하고 있습니다. 여기서 문제가 발생할 수 있습니다. 같은 방식으로 상태를 업데이트하면 동시에 여러 상태 업데이트가 발생할 경우 문제가 될 수 있지요. userInput의 최신 상태를 정확하게 참조하지 못할 가능성이 있어 그 결과 일부 상태 업데이트가 반영되지 않거나, 잘못된 상태로 업데이트될 수 있습니다.

 

 

 

예를 들어 사용자가 다음과 같은 값을 동시에 수행한다고 가정해보겠습니다.

 

handleChange("initialInvestment", 15000) 
handleChange("annulInvestment", 2000)

 

이 경우 setUserInput은 userInput의 최신 상태를 보장하지 못합니다.

 

  • 첫 번째 호출에서 15000으로 initialInvestment가 설정된 이후, 두 번째 호출에서 userInput 상태가 갱신되기 전에 그 이전 상태인 2000으로 annualInvestment가 설정될 수 있습니다.
  • 결과적으로 최종 userInput 상태는 initialInvestment가 초기 값인 10000으로 남아있을 수 있고, annualInvestment만 업데이트될 가능성이 있습니다.

즉 setUserInput 호출에서의 userInput 상태와 두 번째 호출에서의 상태가 충돌할 수 있어서 의도한 대로 결과가 반영되지 못하는 것입니다. 최신 상태를 반영하지 못하는 것이죠. 

 

 

 

대안으로 (prevUserInput) => { ... }  사용

위와 같은 문제를 해결하기 위해서 리액트에서 권장하는 방식은 setUserInput 함수에 콜백함수 '(prevUserInput) => { ... }' 을 사용하는 것입니다. 

 

 

setUserInput 함수에 콜백 함수 (prevUserInput) => { ... }을 전달하는 방식은 항상 최신의 이전 상태 값을 보장합니다. 이 콜백 함수의 인자로 전달되는 prevUserInput은 상태 업데이트가 발생할 때마다 React가 자동으로 현재의 최신 상태 값을 전달해주는 역할을 합니다. 따라서, 여러 상태 업데이트가 동시에 발생해도 안전하게 상태를 업데이트할 수 있습니다.

 

 

다시 올바른 형태로 쓰인 코드를 확인해보겠습니다.

function handleChange(inputIdentifier, newValue) {
  setUserInput((prevUserInput) => {
    // prevUserInput은 항상 최신 상태를 반영함
    return {
      ...prevUserInput,
      [inputIdentifier]: newValue,
    };
  });
}

 

 

위 코드에서 prevUserInput을 사용하면 setUserInput이 호출될 때마다 최신 상태에 기반해 업데이트가 이루어지기 때문에 예기치 않은 상태 누락이나 잘못된 상태 업데이트를 방지할 수 있습니다.

React에서는 상태가 비동기적으로 업데이트 되기 때문에 위와 같은 'prevUserInput'을 사용하면 상태 업데이트가 연속적으로 발생할 때도 안전하게 이전 상태를 반영할 수 있게 되는 것입니다. 

댓글