DEV Community

Cover image for 5 Essential React Hooks to Master State Management
Marcello Novelli
Marcello Novelli

Posted on • Edited on

5 Essential React Hooks to Master State Management

5 Essential React Hooks to Master State Management

I love react hooks and use them all the time. While useState and useEffect are the bread and butter, there are specialized hooks that can make your state management much more elegant and powerful. These are 5 hooks that I find myself using in almost every project, and they can help you write cleaner, more maintainable code.

Want to see more hooks?

If you want to see more hooks like these, check out the unlogg/hooks. It has a growing collection of custom hooks that can help you manage state more effectively in your React applications.

1. useDisclosure - Your Modal's Best Friend

See code for: useDisclosure

Ever found yourself writing the same open/close logic for modals, dropdowns, or any toggle-based UI? useDisclosure is here to save the day.

When to use it:

  • Modals and dialogs
  • Dropdown menus
  • Collapsible sections
  • Any on/off state with callbacks

The Hook:

function useDisclosure(
  initialState: boolean = false,
  options: { onOpen?: () => void; onClose?: () => void } = {}
): [boolean, { open: () => void; close: () => void; toggle: () => void }];
Enter fullscreen mode Exit fullscreen mode

Real-world example:

function MyModal() {
  const [opened, { open, close, toggle }] = useDisclosure(false, {
    onOpen: () => console.log("Modal opened!"),
    onClose: () => console.log("Modal closed!"),
  });

  return (
    <div>
      <Button onClick={open}>Open Modal</Button>
      <Dialog open={opened} onOpenChange={opened ? close : open}>
        <DialogContent>
          <h2>Hello World!</h2>
          <Button onClick={close}>Close</Button>
          <Button onClick={toggle}>Toggle</Button>
        </DialogContent>
      </Dialog>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why it's awesome: No more forgetting to add onOpen callbacks or accidentally creating infinite re-renders. The hook handles all the edge cases for you.

2. useStateHistory - Time Travel for Your State

See code for: useStateHistory

Want to add undo/redo functionality without the complexity? useStateHistory gives you time-travel powers for any state value.

When to use it:

  • Text editors with undo/redo
  • Drawing applications
  • Form wizards
  • Any app where users might want to revert changes

The Hook:

function useStateHistory<T>(
  initialValue: T,
  options: { maxHistorySize?: number } = {}
): [
  T,
  {
    setValue: (value: T | ((prev: T) => T)) => void;
    back: () => void;
    forward: () => void;
    canGoBack: boolean;
    canGoForward: boolean;
    history: T[];
  },
];
Enter fullscreen mode Exit fullscreen mode

Real-world example:

function CounterWithHistory() {
  const [count, { setValue, back, forward, canGoBack, canGoForward }] =
    useStateHistory(0, { maxHistorySize: 10 });

  return (
    <div>
      <h2>Count: {count}</h2>
      <Button onClick={() => setValue((prev) => prev + 1)}>+1</Button>
      <Button onClick={() => setValue((prev) => prev - 1)}>-1</Button>

      <Button onClick={back} disabled={!canGoBack}>
        ← Undo
      </Button>
      <Button onClick={forward} disabled={!canGoForward}>
        Redo →
      </Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pro tip: The hook automatically manages memory by limiting history size, so you don't have to worry about memory leaks in long-running apps.

3. useLocalStorage - Persistent State Made Simple

See code for: useLocalStorage

Tired of losing user preferences on page refresh? useLocalStorage makes state persistence as easy as regular useState.

When to use it:

  • User preferences (theme, language)
  • Shopping cart contents
  • Form draft saving
  • Any state that should survive browser sessions

The Hook:

function useLocalStorage<T>({
  key: string;
  defaultValue: T;
}): [T, (value: T) => void]
Enter fullscreen mode Exit fullscreen mode

Real-world example:

function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage({
    key: "user-theme",
    defaultValue: "dark",
  });

  const toggleTheme = () => {
    setTheme(theme === "dark" ? "light" : "dark");
  };

  return (
    <div className={`app ${theme}`}>
      <Button onClick={toggleTheme}>
        Switch to {theme === "dark" ? "Light" : "Dark"} mode
      </Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The magic: It handles SSR gracefully, manages JSON serialization, and provides error handling out of the box. Your state just works™.

4. useDebounceValue - Performance Optimization Without the Headache

See code for: useDebounceValue

Search boxes, API calls, expensive calculations - useDebounceValue prevents your app from choking on rapid updates.

When to use it:

  • Search inputs
  • API calls triggered by user input
  • Expensive calculations
  • Auto-save functionality

The Hook:

function useDebounceValue<T>(
  initialValue: T,
  delay: number,
  options: { onDebounce?: (value: T) => void } = {}
): [T, { setValue: (val: T) => void }];
Enter fullscreen mode Exit fullscreen mode

Real-world example:

function SearchBox() {
  const [query, setQuery] = useState("");
  const [debouncedQuery, { setValue }] = useDebounceValue("", 300, {
    onDebounce: (value) => {
      if (value) {
        // This only fires 300ms after user stops typing
        fetchSearchResults(value);
      }
    },
  });

  useEffect(() => {
    setValue(query);
  }, [query, setValue]);

  return (
    <div>
      <Input
        placeholder="Search..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <p>Searching for: {debouncedQuery}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Performance win: Instead of making 10 API calls as someone types "javascript", you make just 1 call 300ms after they finish.

5. Bonus: useToggle - The Simplest State Hook

See code for: useToggle

Sometimes you just need a boolean that flips. useToggle is the minimalist's dream.

When to use it:

  • Show/hide toggles
  • Feature flags
  • Simple boolean switches

Quick implementation:

function useToggle(initialValue: boolean = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = useCallback(() => setValue((v) => !v), []);
  return [value, toggle, setValue] as const;
}
Enter fullscreen mode Exit fullscreen mode

Usage:

function FeatureToggle() {
  const [isEnabled, toggle] = useToggle(false);

  return (
    <Button onClick={toggle}>Feature is {isEnabled ? "ON" : "OFF"}</Button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

These 5 hooks solve common state management patterns that you'll encounter in almost every React app:

  1. useDisclosure - Clean modal/toggle state
  2. useStateHistory - Undo/redo functionality
  3. useLocalStorage - Persistent state across sessions
  4. useDebounceValue - Performance optimization for rapid updates
  5. useToggle - Simple boolean state management

Get more hooks!

If you want to see more hooks like these, check out the unlogg/hooks. It has a growing collection of custom hooks that can help you manage state more effectively in your React applications.

Top comments (3)

Collapse
 
dotallio profile image
Dotallio

Really solid picks, especially useStateHistory and useDebounceValue - they've saved me more than once.
Have you tried combining any of these hooks in less typical ways for bigger apps?

Collapse
 
mnove profile image
Marcello Novelli

For some complex projects, yes. Especially useStateHistory hook, which I have modified to handle even more complex cases

Collapse
 
thevaibhavmaurya profile image
Vaibhav Maurya

These hooks are extremely useful. They're going to help write cleaner code. They definitely reduce the effort of creating your own custom hooks.