React|Custom Hooks

useBoolean & useToggle: The Smallest Yet Most Useful Custom Hooks

5
useBoolean & useToggle: The Smallest Yet Most Useful Custom Hooks

Chris is developing a dashboard screen. This screen is packed with UI elements that need to be turned On/Off, such as a 'Sidebar', 'Search Modal', and 'Notification Dropdown'.

Coding without much thought, Chris suddenly realized his code was being plastered with handler functions identical to useState.

typescript
// ❌ Chris's Tedious Repetitive Labor
function Dashboard() {
  // 1. Sidebar State
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const openSidebar = () => setIsSidebarOpen(true);
  const closeSidebar = () => setIsSidebarOpen(false);

  // 2. Search Modal State
  const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
  const openSearchModal = () => setIsSearchModalOpen(true);
  const closeSearchModal = () => setIsSearchModalOpen(false);

  // 3. Notification Dropdown State
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const toggleDropdown = () => setIsDropdownOpen(prev => !prev);

  return (
    // ... JSX Hell ...
  );
}

"I just need to switch true/false, but I have to create 9 functions?"

Naming variables is hard, and typos are easy to make. To eliminate this boring Boilerplate, let's create the smallest but most useful custom hooks in the world.

1. useToggle: The Aesthetics of Simplicity

The simplest form is a Toggle feature that inverts a value.

You just need to wrap useState and export a single toggle function.

typescript
import { useState, useCallback } from 'react';

// initialValue defaults to false
export function useToggle(initialValue: boolean = false) {
  const [value, setValue] = useState(initialValue);

  // Must wrap with useCallback to prevent re-renders when passed as props to child components.
  const toggle = useCallback(() => {
    setValue((prev) => !prev);
  }, []);

  // Return as a tuple [value, toggle] (Similar usage to useState)
  return [value, toggle] as const;
}

Usage Example

typescript
function Checkbox() {
  // Naming becomes much easier.
  const [isChecked, toggleCheck] = useToggle(false);

  return <input type="checkbox" checked={isChecked} onChange={toggleCheck} />;
}

2. useBoolean: Explicit Control

However, UIs like Modals don't just simply toggle.

When a "Cancel" button is pressed, it must close definitively (Set False), and when an "Open" button is pressed, it must open definitively (Set True). Relying only on toggle introduces the hassle of checking the current state.

Therefore, we need useBoolean, an extension of useToggle.

typescript
import { useState, useCallback } from 'react';

export function useBoolean(initialValue: boolean = false) {
  const [value, setValue] = useState(initialValue);

  // 1. Turn On (True)
  const setTrue = useCallback(() => setValue(true), []);
  
  // 2. Turn Off (False)
  const setFalse = useCallback(() => setValue(false), []);
  
  // 3. Invert (Toggle)
  const toggle = useCallback(() => setValue((v) => !v), []);

  // Returning as an object makes it easy to pick only what you need.
  return { value, setValue, setTrue, setFalse, toggle };
}

Refactoring: Chris's Dashboard

Now, let's put Chris's code on a diet using useBoolean.

typescript
// ✅ Cleaned Code
function Dashboard() {
  // Variable names became intuitive (sidebar.value, sidebar.setTrue, etc.)
  const sidebar = useBoolean(false);
  const searchModal = useBoolean(false);
  const dropdown = useBoolean(false);

  return (
    <div>
      {/* Sidebar Control */}
      <button onClick={sidebar.setTrue}>Open Menu</button>
      <Sidebar isOpen={sidebar.value} onClose={sidebar.setFalse} />

      {/* Modal Control */}
      <button onClick={searchModal.setTrue}>Search</button>
      <SearchModal isOpen={searchModal.value} onClose={searchModal.setFalse} />

      {/* Dropdown Toggle */}
      <button onClick={dropdown.toggle}>Notifications</button>
      {dropdown.value && <DropdownList />}
    </div>
  );
}

The code has been cut in half. More importantly, readability has improved. Anyone reading sidebar.setTrue immediately understands, "Ah, this opens the sidebar."

3. Optimization Point: Why is useCallback Necessary?

Looking at the hook implementation above, all handler functions (setTrue, setFalse, toggle) are wrapped in useCallback. Why?

If useCallback is not used, setTrue is re-created (reference address changes) every time the Dashboard component re-renders.

When this function is passed as props to child components like Sidebar or SearchModal, the children think, "Huh? The props changed," and trigger unnecessary re-renders.

When creating custom hooks, memoizing the returned functions is basic etiquette for the user (other developers) and a fundamental optimization practice.

4. Extensibility: Naming Conventions

useBoolean is generic, but you can create hooks with more specific names depending on the situation.

  • useModal: Returns isOpen, open, close.
  • useDisclosure: (Chakra UI style) Returns isOpen, onOpen, onClose.
  • However, the internal logic is ultimately identical to useBoolean. You can just create aliases matching your team's Convention.

    Key Takeaways

  • Problem: Managing boolean state with useState(false) leads to repetitive work creating open, close, and toggle handlers every time.
  • Solution: Create a useBoolean hook to receive the state value and control functions (setTrue, setFalse, toggle) all at once.
  • Benefit: Code becomes concise, and the intent becomes explicit (Semantic), like sidebar.open().
  • Optimization: Wrap returned functions with useCallback to prevent unnecessary re-renders of child components.

  • We warmed up with the easiest hook. Now let's raise the difficulty a bit.

    localStorage is the most used storage in web development. However, using it in React components causes synchronization issues, and it throws errors in SSR environments like Next.js.

    Let's find out how to handle this headache elegantly, just like useState.

    Continuing in: "useLocalStorage: A Browser Storage Hook Safe for Next.js (SSR)."

    🔗 References

  • usehooks-ts: useBoolean
  • Chakra UI - useDisclosure
  • Comments (0)

    0/1000 characters
    Loading comments...