useBoolean & useToggle: 가장 작지만 가장 많이 쓰이는 훅

크리스가 대시보드 화면을 개발하고 있다. 이 화면에는 '사이드바', '검색 모달', '알림 드롭다운' 등 켜고 꺼야(On/Off) 할 UI 요소가 가득하다.
아무 생각 없이 코드를 짜던 크리스는 문득 자신의 코드가 useState와 똑같은 핸들러 함수들로 도배되고 있다는 것을 깨달았다.
// ❌ 크리스의 지겨운 반복 노동
function Dashboard() {
// 1. 사이드바 상태
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const openSidebar = () => setIsSidebarOpen(true);
const closeSidebar = () => setIsSidebarOpen(false);
// 2. 검색 모달 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
const openSearchModal = () => setIsSearchModalOpen(true);
const closeSearchModal = () => setIsSearchModalOpen(false);
// 3. 알림 드롭다운 상태
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const toggleDropdown = () => setIsDropdownOpen(prev => !prev);
return (
// ... JSX 지옥 ...
);
}"고작 true/false 바꾸는 건데, 함수를 9개나 만들어야 해?"
변수 이름 짓기도 힘들고, 오타가 나기도 쉽다. 이 지루한 '보일러플레이트(Boilerplate)'를 없애기 위해, 세상에서 가장 작지만 가장 유용한 커스텀 훅을 만들어보자.
1. useToggle: 단순함의 미학
가장 단순한 형태는 값을 반전시키는 토글(Toggle) 기능이다.
useState를 감싸서 toggle 함수 하나만 내보내면 된다.
import { useState, useCallback } from 'react';
// 초기값(initialValue)은 기본적으로 false
export function useToggle(initialValue: boolean = false) {
const [value, setValue] = useState(initialValue);
// useCallback으로 감싸야 자식 컴포넌트에 props로 넘길 때 리렌더링을 방지할 수 있다.
const toggle = useCallback(() => {
setValue((prev) => !prev);
}, []);
// [값, 토글함수] 튜플 형태로 반환 (useState와 유사한 사용성)
return [value, toggle] as const;
}사용 예시
function Checkbox() {
// 이름 짓기가 훨씬 편해졌다.
const [isChecked, toggleCheck] = useToggle(false);
return <input type="checkbox" checked={isChecked} onChange={toggleCheck} />;
}2. useBoolean: 명시적인 제어
하지만 모달(Modal) 같은 UI는 단순히 토글만 하지 않는다.
"취소" 버튼을 누르면 확실하게 닫아야(Set False) 하고, "열기" 버튼을 누르면 확실하게 열어야(Set True) 한다. 토글만 있으면 현재 상태를 확인해야 하는 번거로움이 생긴다.
그래서 useToggle을 확장한 useBoolean이 필요하다.
import { useState, useCallback } from 'react';
export function useBoolean(initialValue: boolean = false) {
const [value, setValue] = useState(initialValue);
// 1. 켜기 (True)
const setTrue = useCallback(() => setValue(true), []);
// 2. 끄기 (False)
const setFalse = useCallback(() => setValue(false), []);
// 3. 반전 (Toggle)
const toggle = useCallback(() => setValue((v) => !v), []);
// 객체로 반환하면 필요한 것만 꺼내 쓰기 좋다.
return { value, setValue, setTrue, setFalse, toggle };
}리팩토링: 크리스의 대시보드
이제 크리스의 코드를 useBoolean으로 다이어트 시켜보자.
// ✅ 깔끔해진 코드
function Dashboard() {
// 변수명이 직관적으로 변했다. (sidebar.value, sidebar.setTrue 등)
const sidebar = useBoolean(false);
const searchModal = useBoolean(false);
const dropdown = useBoolean(false);
return (
<div>
{/* 사이드바 제어 */}
<button onClick={sidebar.setTrue}>메뉴 열기</button>
<Sidebar isOpen={sidebar.value} onClose={sidebar.setFalse} />
{/* 모달 제어 */}
<button onClick={searchModal.setTrue}>검색</button>
<SearchModal isOpen={searchModal.value} onClose={searchModal.setFalse} />
{/* 드롭다운 토글 */}
<button onClick={dropdown.toggle}>알림</button>
{dropdown.value && <DropdownList />}
</div>
);
}코드가 절반으로 줄었다. 더 중요한 건 가독성이다. sidebar.setTrue라고 적혀 있으면 누구라도 "아, 사이드바를 여는구나"라고 즉시 이해할 수 있다.
3. 최적화 포인트: useCallback이 왜 필요한가?
위의 훅 구현을 보면 모든 핸들러 함수(setTrue, setFalse, toggle)를 useCallback으로 감쌌다. 왜일까?
만약 useCallback을 안 쓰면, Dashboard 컴포넌트가 리렌더링 될 때마다 setTrue 함수도 새로 생성(참조 주소 변경)된다.
이 함수가 자식 컴포넌트인 Sidebar나 SearchModal의 props로 전달되면, 자식 컴포넌트는 "어? props가 바뀌었네?"라고 판단하고 불필요한 리렌더링을 하게 된다.
이처럼 커스텀 훅을 만들 때는 반환하는 함수들을 메모이제이션 해주는 것이 사용자(다른 개발자)에 대한 예의이자 성능 최적화의 기본이다.
4. 확장성: 네이밍 컨벤션
useBoolean은 범용적이지만, 상황에 따라 더 구체적인 이름을 붙여서 훅을 만들 수도 있다.
하지만 내부 로직은 결국 useBoolean과 동일하다. 팀 내에서 사용하는 용어(Convention)에 맞춰 별칭(Alias)을 지어주면 된다.
핵심 정리
가장 쉬운 훅으로 몸을 풀었다. 이제 난이도를 조금 올려보자.
웹 개발에서 가장 많이 쓰는 저장소인 localStorage. 하지만 리액트 컴포넌트에서 쓰기에는 동기화 문제도 있고, Next.js 같은 SSR 환경에서는 에러를 뿜어낸다.
이 골칫덩어리를 useState처럼 우아하게 다루는 방법을 알아보자.
"useLocalStorage: Next.js(SSR)에서도 안전한 브라우저 저장소 훅" 편에서 계속된다.