Previous

React Custom Hooks

  • Custom hooks are essentially JavaScript functions that start with the prefix "use". They allow you to extract component logic into reusable functions, keeping your UI code clean and focused on what to display rather than how to handle data.
  • By leveraging existing hooks like useState and useEffect inside your own functions, you can create a specialized toolkit of logic that can be shared across your entire application.

Purpose

  • Reusability: If you find yourself writing the same useEffect or useState logic in three different components, a custom hook is the solution. It follows the DRY (Don't Repeat Yourself) principle.
  • Maintainability: When logic is isolated in a single hook, fixing a bug or adding a feature to that logic happens in one file rather than hunted down in ten different components.
  • Abstraction: Large components are hard to read. Moving complex data fetching or form validation into a custom hook makes your main component code significantly shorter and easier to understand for your team.
Best Practice: Use custom hooks to separate "logic" from "view." Your component should ideally handle what the user sees, while the hook handles how the data changes.

Syntax

  • Creating a custom hook is straightforward: you write a regular function, but you follow the Rules of Hooks (don't call hooks inside loops or conditions).
  • Always start the function name with use. This isn't just a naming convention; it allows React's linter to check if you are using hooks correctly.
import { useState, useEffect } from 'react';

// The "use" prefix is mandatory for the React linter
const useCustomHook = (initialValue) => {
  const [state, setState] = useState(initialValue);

  useEffect(() => {
    // Example: Subscribing to an external API or setting up a timer
    console.log('Hook initialized');
    
    return () => {
      // Clean up logic to prevent memory leaks
      console.log('Hook cleaned up');
    };
  }, []); // Empty dependency array means this runs once on mount

  // You can return anything: an array, an object, or a single value
  return [state, setState];
};
Common Mistake: Forgetting to start your hook name with "use". If you name it handleStorage instead of useStorage, React won't be able to track state updates correctly, and you won't get warnings about hook violations.

UseLocalStorage Hook

  • Storing data in localStorage is a common task. However, keeping that storage in sync with your React state can be tedious. This hook automates that synchronization.
import { useState, useEffect } from 'react';

const useLocalStorage = (key, initialValue) => {
  // Get initial state from local storage or use the provided initialValue
  const [value, setValue] = useState(() => {
    try {
      const storedValue = localStorage.getItem(key);
      return storedValue ? JSON.parse(storedValue) : initialValue;
    } catch (error) {
      console.error("Error reading localStorage key:", key, error);
      return initialValue;
    }
  });

  // Update local storage whenever the key or value changes
  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error("Error setting localStorage key:", key, error);
    }
  }, [key, value]);

  return [value, setValue];
};

Usage

// Use it exactly like useState, but the value persists on page refresh!
const [theme, setTheme] = useLocalStorage('app-theme', 'light');
Watch Out: LocalStorage only stores strings. That's why we use JSON.stringify and JSON.parse. Also, remember that LocalStorage is synchronous and can block the main thread if you store massive amounts of data.

useToggle Hook

  • One of the most frequent state changes in React is switching a boolean between true and false (e.g., opening a modal, showing a dropdown, or expanding a menu).
import { useState, useCallback } from 'react';

const useToggle = (initialState = false) => {
  const [isToggled, setIsToggled] = useState(initialState);

  // We wrap the toggle function in useCallback to prevent 
  // unnecessary re-renders in child components
  const toggle = useCallback(() => {
    setIsToggled((prev) => !prev);
  }, []);

  return [isToggled, toggle];
};

Usage

const [isModalOpen, toggleModal] = useToggle(false);

return (
  <button onClick={toggleModal}>
    {isModalOpen ? 'Close Modal' : 'Open Modal'}
  </button>
);
Developer Tip: The useToggle hook is a classic "Swiss Army Knife" utility. It’s perfect for handling Navigation menus, Accordions, and Password visibility toggles.

UseMediaQuery Hook

  • While CSS media queries handle styling, you often need to change logic or components based on screen size (e.g., rendering a MobileNav vs. a DesktopNav).
import { useState, useEffect } from 'react';

const useMediaQuery = (query) => {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const mediaQueryList = window.matchMedia(query);
    
    // Set the initial value
    setMatches(mediaQueryList.matches);

    // Modern way to listen for changes
    const listener = (event) => setMatches(event.matches);
    mediaQueryList.addEventListener('change', listener);

    return () => {
      // Clean up the listener when the component unmounts
      mediaQueryList.removeEventListener('change', listener);
    };
  }, [query]);

  return matches;
};

Usage

const isMobile = useMediaQuery('(max-width: 768px)');

return (
  <div>
    {isMobile ? <MobileSidebar /> : <DesktopSidebar />}
  </div>
);
Best Practice: Always clean up event listeners in your useEffect return function. Failing to do so can cause "memory leaks," where your app gets slower over time as it tries to run listeners for components that are no longer on the screen.

 

Summary

Custom hooks aren't just a "nice-to-have" feature; they are the backbone of modern React development. By encapsulating logic into hooks like useLocalStorage or useMediaQuery, you create a codebase that is modular, testable, and significantly easier to read. As your application grows, look for patterns in your components any logic that appears twice is a prime candidate for a custom hook.

Previous