[리팩토링] useToggle 훅 만들어 재사용하기

지은·2023년 5월 18일
0

🌴 모동숲 마켓

목록 보기
8/9

MyPage 컴포넌트에는 boolean 값을 가지는 상태가 2개가 있다.

  • 프로필을 수정 중인지 아닌지를 나타내는 상태 isEditing
  • 모달이 열려있는지 닫혀있는지를 나타내는 상태 isModalOpen

두 상태 모두 같은 기능을 하는 토글 상태이므로 재사용 가능한 useToggle 훅을 만들어서 컴포넌트를 간소화해보려고 한다.

리팩토링 전

MyPage.tsx

const MyPage = () => {
	const [isEditing, setIsEditing] = useState(false); // 프로필 수정 버튼을 눌러 편집창을 껐다 켰다 할 수 있는 상태
  	const [isModalOpen, setIsModalOpen] = useState(false); // 모달을 껐다 켰다 할 수 있는 상태. 모달에는 로그아웃, 회원탈퇴 버튼이 있음
  
  	// 프로필을 수정하고 변경한 내용을 submit하는 함수
  	const onSubmit = () => {
      // 생략...
      setIsEditing(false);
    }
    
    // 프로필 수정창에서 '취소' 버튼에 연결된 이벤트 핸들러
    const onCancel = () => {
      setIsEditing((isEditing) => !isEditing);
    }
  
    // 모달 껐다 켰다할 수 있는 버튼에 연결된 이벤트 핸들러
  	const handleModal = () => {
      setIsModalOpen((isModalOpen) => !isModalOpen);
    }
    
    // 모달 영역 밖을 클릭하면 isModalOpen을 false로 업데이트해주는 함수
    const handleOutsideClick = (e: React.MouseEvent<HTMLDivElement>) => {
		const target = e.target as any;
		if (isModalOpen && !modalRef.current?.contains(target)) {
			setIsModalOpen(false);
		}
	};
    
    return (// 생략...);
}


리팩토링 후

useToggle.ts

import { useState } from 'react';

function useToggle(initialValue = false) { // 초기값을 매개변수로 받는다. 초기값이 없는 경우 기본값으로 false를 준다.
	const [status, setStatus] = useState<boolean>(initialValue);

	const toggleStatus = () => setStatus((prevStatus) => !prevStatus);

	return [status, toggleStatus];
}

export default useToggle;

MyPage.tsx

const MyPage = () => {
	const [isEditing, toggleIsEditing] = useToggle(false);
	const [isModalOpen, toggleIsModalOpen] = useToggle(false);
  
  	// 프로필을 수정하고 변경한 내용을 submit하는 함수
  	const onSubmit = () => {
      // 생략...
      toggleIsEditing();
    }
    
    // 프로필 수정창에서 '취소' 버튼에 연결된 이벤트 핸들러
    const onCancel = () => {
      toggleIsEditing();
    }
  
    // 모달 껐다 켰다할 수 있는 버튼에 연결된 이벤트 핸들러
  	const handleModal = () => {
      toggleIsModalOpen();
    }
    
    // 모달 영역 밖을 클릭하면 isModalOpen을 false로 업데이트해주는 함수
    const handleOutsideClick = (e: React.MouseEvent<HTMLDivElement>) => {
		toggleIsModalOpen();
	};
    
    return (// 생략...);
}

매우 매우 직관적이고 깔끔해졌다.!
하지만 이렇게 하고 실행하니 에러를 만났는데..


에러 해결

"This express is not callable. Type '...' has no call signatures"

This expression is not callable.
  Not all constituents of type 'boolean | (() => void)' are callable.
    Type 'false' has no call signatures.
    27 |
    28 | 	const handleModal = () => {
  > 29 | 		toggleIsModalOpen();
       | 		^^^^^^^^^^^^^^^^^
    30 | 	};

이 에러는 함수로 간주되지 않는 타입에 대해 호출 연산자()를 사용하려고 할 때 발생한다.
즉, toggleIsModalOpen()이 타입스크립트에게 함수로 인식되고있지 않다는 뜻이다.

그 이유는 useToggle 훅에서 상태와 함수를 배열로 반환하고 있기 때문이다..

return [status, toggleStatus]

그래서 아래와 같이 useToggle 훅에 타입 지정을 추가해줬더니, 타입스크립트가 반환 타입을 추론할 수 있게 되며 에러가 사라졌다.

import { useState } from 'react';

type ToggleHookType = [boolean, () => void]; // 타입 추가

function useToggle(initialValue = false): ToggleHookType { // 타입 지정
	const [status, setStatus] = useState<boolean>(initialValue);

	const toggleStatus = () => setStatus((prevStatus) => !prevStatus);

	return [status, toggleStatus];
}

export default useToggle;
profile
블로그 이전 -> https://janechun.tistory.com

4개의 댓글

comment-user-thumbnail
2023년 5월 19일

함수로 인식안하면 당황스럽던데 잘 해결하셨네요 고생하셨습니다.

답글 달기
comment-user-thumbnail
2023년 5월 21일

깔끔하게 잘 하셨네용 수고하셨슴돠 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 5월 21일

고생하셨습니다 !

답글 달기
comment-user-thumbnail
2023년 5월 21일

타입스크립트는 귀찮아요 아주 타입 선언하기.. 잘보고 갑니당

답글 달기