프론트엔드/UMC-8기

[React] DOM 업데이트와 댓글 스크롤 타이밍 문제 해결기

icems0428 2025. 8. 15. 00:11

우리가 구현해야 하는 댓글 생성 및 조회 흐름은 사용자 기준에서 다음과 같았다.

1. 댓글 입력창은 항상 화면 하단에 고정되어 있어야함.

2. 댓글 목록만 따로 스크롤 되는 것 없이 모두 노출되고, 전체 페이지 기준으로 스크롤 되어야함.

3. 댓글 생성시 "최신 댓글(화면 하단)"으로 자동 스크롤

사진으로 보면 이렇다.

즉, 댓글 입력창이 항상 고정되고, 댓글 추가 시 자동으로 가장 하단 댓글이 보여야 한다는 UI 구조이다.

처음엔 그저 화면 맨 끝으로 이동하면 되니까 간단할 줄 알았지만, 생각보다 고려해야 할 게 많았다.


먼저 시도해본 방법은, useRef와 scrollIntoView를 이용해서 해당 부분으로 스크롤하는 것이었다. 참고한 문서는 아래와 같다.

https://developer.mozilla.org/ko/docs/Web/API/Element/scrollIntoView

 

element.scrollIntoView - Web API | MDN

Element 인터페이스의 scrollIntoView() 메소드는 scrollIntoView()가 호출 된 요소가 사용자에게 표시되도록 요소의 상위 컨테이너를 스크롤합니다.

developer.mozilla.org

https://bayaa.tistory.com/16

 

[React] div 스크롤 맨 밑으로 내리기 / 스크롤 위치 조작하기 - useRef

실시간 채팅창을 구현하다가 마주한 문제였다. 따로 설정을 해두지 않으면 채팅방을 처음 열었을 때 스크롤이 맨 위에서부터 시작되어 최근 메세지를 보려면 직접 사용자가 스크롤을 내려야했

bayaa.tistory.com

 

내가 작성한 코드를 간략히 정리하면 다음과 같다.

// 이슈 상세 페이지 컴포넌트
const IssueDetail = () => {
    const bottomRef = useRef<HTMLDivElement | null>(null);

    const handleAddComment = async (content: string) => {
        await addComment(content);
        // 댓글 추가시 bottomRef로 스크롤
        bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
      };

      return (
        <div className="flex flex-1 flex-col min-h-max gap-[5.7rem] w-full px-[3.2rem] pt-[3.2rem]">
              <div className="flex flex-col min-h-max gap-[1.6rem]">
                {/* 댓글 영역 */}
                {isCompleted && <CommentSection />}
                {/* 댓글 작성 영역 */}
                {isCompleted && (
                  <div className="sticky bottom-[0rem] z-20 bg-white">
                    <CommentInput onAdd={handleAddComment} />
                    <div className="h-[5.3rem] bg-transparent"></div>
                  </div>
                )}
              </div>
            </div>
            
          {/* 페이지 맨 하단에 bottomRef 설정 */}
          <div ref={bottomRef} className="scroll-mb-[6.4rem]" />
        </div>
      );
}​

 

스크롤은 되지만, 문제가 발생했다. 바로 "댓글 1개의 공간"만큼 스크롤이 "덜" 되는 문제였다. 그래서 스크롤을 추가로 조금 더 내려줘야 가장 하단에 있는 최신 댓글을 볼 수가 있었다.

딱 1개의 공간만큼만 안되는 문제를 미루어 볼 때, 스크롤이 실행될 때 가장 최신 댓글이 반영이 안 되고 있다는 게 가장 합리적인 원인 같았다.

 

이런 복합적인 문제는 구글링을 해도 잘 안 나와서, AI를 적극 이용했다.

도출한 예상 상세 원인은 다음과 같았다.

await로 댓글 추가 함수가 실행되길 기다리기는 했지만, 새로운 댓글이 DOM에 반영되기 전 시점으로 스크롤이 이루어지는 것이다. 리액트의 DOM 시스템을 모르면 이걸 이해하는 데에도 한참이 걸릴 뻔했다.

 

React는 상태 변경 → 렌더링 → DOM 반영 → 브라우저 그리기 단계로 진행된다.
비동기 작업 후에도 DOM 반영이 보장되지 않은 시점에 스크롤이 발생하기 때문에 오류가 발생한 것이었다.

이걸 보고 컴퓨터 구조 시간에 배운 파이프라인 스톨(Pipeline Stall) 개념이 떠올랐다.

 

컴퓨터 구조 파이프라이닝(Pipelining)에서는 명령어를 여러 단계로 나누고, 각 단계를 동시에 처리하여 처리 속도를 높인다. 하지만, 데이터 의존성(Data Hazard)이 일어날 때에는 파이프라인 스톨을 삽입해서 잠시 멈추는 기법이 있다.

이것이 내게 발생한 문제와 매우 유사하다고 느꼈고, requestAnimationFrame을 이용하여 렌더링이 완료된 시점까지 대기해야 스크롤이 제대로 이루어질 수 있음을 이해했다.

 

그래서 수정한 handleAddComment 이벤트 핸들러 함수는 다음과 같다.

const handleAddComment = async (content: string) => {
  await addComment(content);

  // 다음 프레임까지 기다리는 함수
  const waitNextPaint = () =>
    new Promise<void>((resolve) => {
      requestAnimationFrame(() => {
        if (useDoubleRaf) {
          requestAnimationFrame(() => resolve());
        } else {
          resolve();
        }
      });
    });

  await waitNextPaint();

  // 댓글 추가시 bottomRef로 스크롤
  bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
};

 

scrollIntoView 전에 두 번의 RAF를 사용하는 건, React에서 렌더가 완료되고 DOM이 실제로 반영된 후에 스크롤하도록 하기 위함이다.

 

 

✅ 최종 해결 요약

  • addComment 후 DOM 반영 시점을 기다리기 위해 requestAnimationFrame을 사용
  • 두 번 사용하는 이유는 React 렌더링 → 브라우저 Paint까지의 완전한 완료를 보장하기 위함
  • scrollIntoView는 이 시점 이후에 호출해야 정확히 스크롤이 작동

 

관련된 다음 트러블 슈팅 보기:

https://icems0428.tistory.com/26

 

Mac OS에서 사이드바로 스크롤 전파되는 문제 해결하기

지난 게시글에서, 댓글 추가 시 화면 하단으로 스크롤 되도록 무사히 구현을 마쳤다.그런데 어느날 우리 팀원이 보내준 사진이다.사이드바 영역이 잘린 채로 표시되고 있었다. 분명 내 컴퓨터에

icems0428.tistory.com