Custom Hooks

Back

Loading concept...

🪄 Custom Hooks: Building Your Own Magic Spells

Imagine you’re a wizard. React gives you some basic spells like useState (remember things) and useEffect (do things when stuff changes). But what if you could create your own spells that combine these powers? That’s exactly what Custom Hooks are!


🎯 The Big Picture

Think of custom hooks like recipe cards in a kitchen. Instead of remembering every step to make chocolate cake each time, you write it down once. Then anyone can follow your recipe!

graph TD A["🧙 Basic Hooks"] --> B["✨ Custom Hook"] B --> C["📱 Component 1"] B --> D["📱 Component 2"] B --> E["📱 Component 3"]

One recipe. Used everywhere. No copy-pasting!


📜 Hook Rules: The Wizard’s Code

Before creating spells, every wizard must learn The Rules. Break them, and your magic won’t work!

Rule 1: Only Call Hooks at the Top Level

❌ Wrong - Inside a loop:

function Bad() {
  for (let i = 0; i < 3; i++) {
    useState(i); // BOOM! Broken!
  }
}

✅ Right - At the top:

function Good() {
  const [count, setCount] = useState(0);
  // Now use count in loops
}

Rule 2: Only Call Hooks from React Functions

Hooks work inside:

  • React function components
  • Custom hooks (starting with use)

❌ Wrong:

function regularFunction() {
  useState(0); // Nope!
}

✅ Right:

function MyComponent() {
  useState(0); // Perfect!
}

Why These Rules?

React remembers hooks by the order you call them. It’s like a checklist:

Call 1: useState for name ✓
Call 2: useState for age ✓
Call 3: useEffect for data ✓

If you put hooks in if statements, the order might change. React gets confused!


🔨 Creating Custom Hooks

Here’s the magic formula:

  1. Name starts with use (like useCounter)
  2. Can use other hooks inside
  3. Returns whatever you want

Your First Custom Hook

Let’s make a hook that tracks window size:

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener(
      'resize', handleResize
    );
  }, []);

  return size;
}

Using it is simple:

function MyApp() {
  const { width, height } = useWindowSize();
  return <p>Screen: {width} x {height}</p>;
}

That’s it! No copy-pasting. Just use useWindowSize() anywhere!


🧩 Hook Composition: Combining Spells

The real magic happens when hooks use other hooks. It’s like building with LEGO!

graph TD A["useLocalStorage"] --> B["useState"] C["useFetch"] --> D["useState"] C --> E["useEffect"] F["useAuth"] --> A F --> C

Example: A Smart Counter

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initial;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

function useCounter(key) {
  const [count, setCount] = useLocalStorage(key, 0);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(0);

  return { count, increment, decrement, reset };
}

Now useCounter remembers its value even after page refresh!


♻️ Reusable Logic: Write Once, Use Forever

Custom hooks solve the DRY problem (Don’t Repeat Yourself).

Before Custom Hooks 😰

// Component A
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => { fetch(url)... }, []);

// Component B - Same code again!
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => { fetch(url)... }, []);

After Custom Hooks 😊

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

Now everywhere:

const { data, loading } = useFetch('/api/users');

Clean. Simple. Reusable!


🔍 useDebugValue: Peek Behind the Curtain

When you have many custom hooks, debugging can get tricky. useDebugValue shows helpful labels in React DevTools.

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  // Shows "Online" or "Offline" in DevTools
  useDebugValue(isOnline ? 'Online' : 'Offline');

  useEffect(() => {
    const update = () => setIsOnline(navigator.onLine);
    window.addEventListener('online', update);
    window.addEventListener('offline', update);
    return () => {
      window.removeEventListener('online', update);
      window.removeEventListener('offline', update);
    };
  }, []);

  return isOnline;
}

Lazy Formatting (For Heavy Work)

If creating the debug label is slow, pass a function:

useDebugValue(data, d => formatExpensiveData(d));

React only calls this when DevTools is open!


🔗 useSyncExternalStore: Talking to the Outside World

Sometimes you need to read data from outside React—like browser APIs, third-party libraries, or global stores.

useSyncExternalStore is the safe way to do this.

graph LR A["External Store"] -->|subscribe| B["useSyncExternalStore"] B -->|getSnapshot| C["Component"]

Example: Online Status Store

function subscribe(callback) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

function getSnapshot() {
  return navigator.onLine;
}

function useOnlineStatus() {
  return useSyncExternalStore(subscribe, getSnapshot);
}

Why Not Just useEffect?

useSyncExternalStore handles tricky timing issues that useEffect can miss, especially with server rendering. It’s bulletproof for external data!

The Three Parts

Part What It Does
subscribe Listens for changes
getSnapshot Gets current value
getServerSnapshot Value for server (optional)

🌟 Putting It All Together

Let’s build a complete hook that does everything:

function useLocalStorageSync(key, initial) {
  // Subscribe to storage changes
  const subscribe = useCallback((callback) => {
    window.addEventListener('storage', callback);
    return () => {
      window.removeEventListener('storage', callback);
    };
  }, []);

  // Get current value
  const getSnapshot = useCallback(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initial;
  }, [key, initial]);

  // Safe external store sync
  const value = useSyncExternalStore(
    subscribe,
    getSnapshot
  );

  // Setter function
  const setValue = useCallback((newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue));
    window.dispatchEvent(new Event('storage'));
  }, [key]);

  // Debug label
  useDebugValue(value, v => `${key}: ${JSON.stringify(v)}`);

  return [value, setValue];
}

This hook:

  • ✅ Follows all hook rules
  • ✅ Composes multiple hooks
  • ✅ Creates reusable logic
  • ✅ Uses useDebugValue for debugging
  • ✅ Uses useSyncExternalStore for external sync

🎓 Key Takeaways

Concept One-Liner
Hook Rules Top-level only, React functions only
Custom Hooks Functions starting with use that use hooks
Composition Hooks can use other hooks
Reusable Logic Write once, use everywhere
useDebugValue Labels for React DevTools
useSyncExternalStore Safe way to read external data

🚀 You’re Now a Hook Wizard!

You’ve learned to:

  1. Follow the rules so React trusts you
  2. Create custom hooks to package your logic
  3. Compose hooks to build powerful tools
  4. Share logic across your entire app
  5. Debug with helpful labels
  6. Connect safely to external stores

Now go build your own magical hooks! 🧙‍♂️✨

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.