개발자
류준열

자동저장 기능 구현시 고려해야 할 것

  1. 몇 초 간격으로 자동저장 되도록 하는 기능을 구현하려면 가장 먼저 떠오르는게 setInterval이다. 이때 컴포넌트 state가 변경되어도 초기 렌더링 시점의 함수를 참조하기 때문에 최신 상태를 잘 반영하는 구조인지 살펴야 한다. 이를 해결하기 위해서는 아래 소개할 useInterval을 쓰면 된다.

  2. 유저가 브라우저 탭을 여러개 켜 놓았을 경우, 백그라운드 탭을 다시 켰을때 자동저장기능이 기존 입력값을 덮어씌우지 않도록 해야 한다.

useInterval

  useEffect(() => {
    const autoSaveTimer = setInterval(autoSaveCallback, 1000 * 10);
    return () => clearInterval(autoSaveTimer);
  }, []);

10초마다 자동저장하는 기능 구현을 위해, 위와 같이 하면 될 것 같지만 안된다.
왜냐하면 컴포넌트 내에서 autoSaveCallback이 업데이트 되었을때 setInterval은 오래된 클로저가 되어 최신상태를 반영하지 못하기 때문이다.

그렇다고해서 의존성배열에 autoSaveCallback을 넣으면 autoSaveCallback이 새로 생성될 때 마다(아마 리렌더링마다) 리셋되어 타이머가 정상작동하지 않을 것이다.

Dan 형님께서 친히 만들어주신 useInterval.tsx 라는 것이 있어서 이것을 사용하면 된다.

useInterval을 사용하면 다음과 같이 작성할 수 있다.

  useInterval(autoSaveCallback,10*1000)

다중탭으로 인한 초기화

간헐적으로 지원서에 적었던 내용이 다 사라졌어요. 라는 문의가 들어왔다.

알고보니 다중탭을 켜둔 유저에게 발생하는 버그였다.

10초 간격으로 유저의 답변을 자동저장하는 경우, 두 개 이상의 탭에 똑같은 질문지들을 켜놓고 한쪽 탭에서만 질문지를 제출하면 '빈 질문지' 와 '작성중인 질문지'가 연달아 자동저장되다가 마지막에 '작성완료한 질문지'를 '빈 질문지'가 덮어씌우면서 모든 답변이 초기화되었다.

이를 해결하기 위해서 브라우저,서버 양 측에서의 validation을 추가하였다.

프론트

document.hidden 을 이용했다.

  useInterval(() => {
    if (document.hidden) {
    // 활성화 되어 있지 않으면 아무것도 안함.
      return;
    }
    recordApplicationAnswer(survey, 'save');
  }, 10 * 1000);

백엔드

서버에서 기억하는 상태와 실제 DB를 비교해서 값이 다르면 에러를 반환하기로 하고, 프론트에서는 해당 에러를 받을때 모달을 띄워주기로 했다.


  // 백엔드 수정에 따른 프론트 에러처리

  const handleAutoSaveError = (err: AxiosError) => {
    if (isErrorModalShowed) return;

    const { message } = (err as unknown as AxiosError).response?.data;

    switch (message) {
      case '이전 상태가 일치하지 않습니다.':
        return Modal.error({
          title: '지원 안내',
          content:
            '지원자님의 응답을 안전하게 저장하기 위해 마이페이지로 이동합니다. 마이페이지에서 진행 현황을 확인해주세요.',

          okText: '확인',
          onOk: () => {
            history.push(`/mypage`);
            setIsErrorModalShowed(false);
          }
        });
      ...
    }
  };

예를 들어 DB에는 질문지의 상태가 SUBMITTED 로 저장되어 있는데, IN_PROGRESS 가 들어오면 다중탭 에러로 판단하고 모달로 안내하는 것이다.

정리

  • 다중탭의 경우 활성화되지 않은 탭에서 자동저장이 실행되지 않도록 하는 것
  • setInterval이 react의 라이프사이클과 맞지 않기 때문에 useInterval을 만들어 쓰는 것

시간이 지난 후 추가 작성

노션 같은건 어떻게 할 까 고민하다가 사람들한테 물어보았는데 서버에서 timestamp를 저장한다고 한다.
그래서 timestamp를 비교해서 마지막 수정 시간이 DB에 저장된 것 보다 오래된 내용이면 서버에서 거부하고, 프론트에서는 최신 자동 저장된 내용으로 덮어 씌운다고 한다.