[React] Debouncing을 활용한 Modal 구현 - useCallback #2

김태훈·2022년 2월 14일
0

React

목록 보기
8/9

오늘은 프로젝트를 개발할 때 빠질 수 없는 Modal창 구현에 대해 말해보고자 하는데요. 단순히 Modal만 구현을 하는 것이 아니라 Debouncing을 활용해 Modal창 활성화 버튼을 계속 눌렀을 때 해당 창이 꺼지지 않고 지속적으로 남아 있을 수 있도록 만드는 법에 대해 알아보도록 하겠습니다.

먼저, Debouncing에 대해 궁금하신 분들은 제가 썼던 바로 전 글을 읽어주시면 감사하겠습니다.

[React] 디바운싱을 이용한 API 요청

이번 Modal창 만드는 작업을 할 때에는 Lodash 라이브러리를 사용하지 않고 clearTimeout()과 setTimeout()을 활용해 만들어보겠습니다.

기존 모달창은 다음과 같습니다.

import { ConfirmModal } from '../SomeFolder/SomeFile'

const ModalEample () => {
  const [showLoginModal, setShowLoginModal] = useState(false);
  const text = "모달창 띄우기";
  
  const onClickModal = () => {
    setShowLoginModal(true);
    setTimeout(() => {
      setShowLoginModal(false);
    }, 2000);
  };
  
  return (
    <>
        <button onClick={onClickModal}> 모달 띄우기 <button/>
	    {showLoginModal && (<ConfirmModal text={text}/>)}
    </>
  );
}

다음과 같은 컴포넌트가 있다고 할 때, setTimeout()을 통해 버튼이 클릭되고 열린 후에 2000ms가 지나면 해당 ConfirmModal은 닫히게 됩니다. 하지만 어떤 사용자가 버튼을 계속 누르면 어떻게 될까요?

onClickModal()을 통해 ture값을 가지게 된 showLoginModal은 사용자가 아무리 많이 버튼을 누른다고 해도 한 번 열린 모달을 계속 떠있게 할 수 없고 2000ms가 지나면 닫히게 됩니다.

이런 현상은 사용자 입장에서 직관적으로 와닿지 못하는 상황이기에 사용자가 누르면 그때부터 다시 setTimeout을 설정해주는 편이 자연스럽습니다.

이럴 때 사용할 수 있는 것은 바로 저번에도 다루었었던 clearTimeout()과 setTimeout()을 할당할 수 있는 변수 timer를 선언해주는 것인데요.

이 과정까지 적용해보도록 하겠습니다.

import { ConfirmModal } from '../SomeFolder/SomeFile'

const ModalEample () => {
  const [showLoginModal, setShowLoginModal] = useState(false);
  const text = "모달창 띄우기";
  
  let timer;
  const onClickModal = () => {
    setShowLoginModal(true);
    
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      setShowLoginModal(false);
    }, 2000);
  };
  
  return (
    <>
        <button onClick={onClickModal}> 모달 띄우기 <button/>
	    {showLoginModal && (<ConfirmModal text={text}/>)}
    </>
  );
}

위처럼 clearTimeout()과 timer를 사용해서 Debouncing을 구현해줬는데요. 작동을 잘 할까요?

답은 작동하지 않는다. 입니다.

왜냐하면 해당 onClickModal() 함수가 작동하면서 setShowLoginModal의 값을 바꾸고 이는 다시 컴포넌트가 리렌더링되게 하기 때문입니다. 컴포넌트가 다시 렌더링되면서 기존에 남아있는 setTimeout()을 제어하지 못한 채 실행되어 지속적으로 클릭을 했음에도 불구하고 2초마다 모달이 꺼지게 되는 것입니다.

그렇다면 이를 막을 수 있는 방법은 무엇이 있을까요?

useCallback

useCallback()은 함수를 메모이제이션(memoization)하기 위해서 사용되는 hook 함수

위에 나온 useCallback을 활용해 해결할 수 있습니다.

const add = useCallback(() => x + y, [x, y]);

이처럼 사용하게 될 경우 컴포넌트가 새로 렌더링되게 되더라도 x와 y의 값이 바뀌지 않으면 기존 함수를 계속해서 반환합니다.

이 개념을 활용해 컴포넌트가 렌더링 되더라도 timer가 바뀌지 않으면 기존 함수를 계속 사용할 수 있도록 적용해보겠습니다.

import { useCallback } from 'react'
import { ConfirmModal } from '../SomeFolder/SomeFile'


const ModalEample () => {
  const [showLoginModal, setShowLoginModal] = useState(false);
  const text = "모달창 띄우기";
  
  let timer;
  const onClickModal = useCallback(() => {
    setShowLoginModal(true);
    
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      setShowLoginModal(false);
    }, 2000);
  }, [timer]);
  
  return (
    <>
        <button onClick={onClickModal}> 모달 띄우기 <button/>
	    {showLoginModal && (<ConfirmModal text={text}/>)}
    </>
  );
}

이와 같이 사용하게 되면 컴포넌트가 리렌더링 된다고 할지라도 사용자가 버튼을 지속적으로 눌렀을 때 제대로 Debouncing되게 할 수 있습니다.

profile
1일 1커밋 1블로그!

0개의 댓글