[React] 효과적이고 안전하게 useState로 값을 업데이트 하는 방법 (feat. 함수형 업데이트)

sujipark-fe·2024년 7월 19일

React

목록 보기
4/7
post-thumbnail

이슈 상황

react 개발을 하다보면 활성/비활성 토글을 빠르게 연속하여 클릭 시, 값이 제대로 저장되지 않은 적이 있지 않으신가요?

const [enable, setEnable] = useState(initialEnable);

onChange={() => setEnable(!enable)}

보통 이렇게 onClick 이나 onChange 이벤트로 useState를 활용하여 값을 업데이트 해주게 됩니다.
이런 경우 useState의 값이 원하는 대로 적용되지 않는 이슈가 발생할 수 있습니다.

원인

상태 업데이트의 비동기성
React의 상태 업데이트는 비동기적으로 처리될 수 있습니다. 위 방법에서는 enable 상태가 아직 업데이트되지 않은 상태를 참조할 수 있기 때문에, 연속된 상태 업데이트에서 예기치 않은 동작을 일으킬 수 있습니다.

해결

onChange={() => setEnable((prevEnable) => !prevEnable)}

동기성을 보장하여 항상 상태의 최신 값 사용
위 방법은 함수형 업데이트를 사용하여 항상 최신 상태 값을 참조합니다. prevEnable은 React가 상태 업데이트를 위해 제공하는 최신 상태 값이므로, 연속된 상태 업데이트에서도 올바르게 동작합니다.
(prevEnable 이 아니라도 이름은 직접 지정가능합니다.)

상태 토글 예제

위에서 말했던 활성/비활성 토글 예제 코드로 볼까요?

const [enable, setEnable] = useState(initialEnable);

<input
  type="checkbox"
  checked={enable}
  onChange={() => setEnable((prevEnable) => !prevEnable)}
/>

만약 enable 값이 변경될 때 마다 user 정보와 enable값을 저장하는 경우라면 이렇게 작성할 수 있습니다.

const [user, setUser] = useState(initialUser);

useEffect(() => {
  setUser((prevUser) => ({
    ...prevUser,
    enable: enable
  }));
}, [enable]);

1) 상태 관리: useState를 사용하여 user와 enable 상태를 관리합니다. 그리고 ...연산자로 이전 user정보는 유지하고, enable 값만 추가해주는거죠.
2) 상태 동기화: useEffect를 사용하여 enable 상태가 변경될 때마다 user 객체를 업데이트합니다.

파일 업로드 시 상태 업데이트 예제

또 다른 상황으로는 위에서 업데이트 한 useState 값을 아래에서 다른 값을 추가하여 업데이트 해주는 경우, 위에서 적용한 기존 값이 나오지 않는 경우가 있습니다.
파일 업로드를 하는 코드를 예시로 보면, 파일의 이름(uploadFile.name)을 setUploadFile에 먼저 저장하고, 파일의 타입에 따라 다른 처리를 하여 파일의 내용(uploadFile.content)는 이후에 저장을 해주게 됩니다.
이 때 파일 내용(uploadFile.content)만 저장되고, 파일의 이름(uploadFile.name)은 저장되지 않는 이슈를 발견했습니다.

const [uploadFile, setUploadFile] = useState({
  name: '',
  content: '',
});

const handleDropFile = useCallback((acceptedFiles) => { 
  let file = acceptedFiles[0];
  const reader = new FileReader();

  // 파일 정보
  const fileName = file.name;

  // 파일명 저장
  setUploadFile({ // ⬅️ 1번 상태 업데이트
    name: fileName
  });

  reader.onload = async () => {
    if (ext === 'pdf') {
      // pdf 파일 처리
    } else {
      // 이외 파일타입 처리
      const text = await file.text();

      // 파일 내용 저장
      setUploadFile({  // ⬅️ 2번 상태 업데이트
        ...uploadFile,
        content: text,
      });
    }
  };

  reader.readAsArrayBuffer(file); // 파일 읽기 시작
}, []);

이 경우 uploadFile의 최신 값을 사용하려면 이전 상태를 기반으로 새로운 상태를 계산해야 합니다. 즉, 2번 상태 업데이트 코드를 이렇게 수정하면 해결할 수 있습니다.

setUploadFile((prevUploadFile) => ({
  ...prevUploadFile,
  content: text,
}));

결론

따라서, 상태 업데이트 함수가 이전 상태를 기반으로 새로운 상태를 계산해야 하는 경우에는 항상 함수형 업데이트를 사용하는 것이 좋습니다.

profile
개발 너무 재밌다 재밌어❤️‍🔥

0개의 댓글