Skip to content
intermediatePhase 36 · React

Custom Hooks

Extract reusable logic into custom hooks for cleaner component code.

45m
0 problems
Topic Progress0%

Creating Custom Hooks

Creating Custom Hooks

Custom hooks are functions that start with "use" and can call other hooks.

Basic Custom Hook

import { useState, useEffect } from "react";

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

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

  return [value, setValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage("name", "");

  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
      placeholder="Enter name"
    />
  );
}

Data Fetching Hook

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

  useEffect(() => {
    const controller = new AbortController();

    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error("Failed to fetch");
        const json = await response.json();
        setData(json);
      } catch (err) {
        if (err.name !== "AbortError") {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchData();

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name}</div>;
}

Naming Conventions

Naming Conventions

Rules

  1. Must start with "use"
  2. Follow camelCase
  3. Be descriptive

Good Names

// Good
useLocalStorage
useFetch
useDebounce
useToggle
usePrevious
useWindowSize
useMediaQuery
useForm

// Bad
localStorageHook
fetchData
toggleState
previous

Naming Pattern

// Pattern: use + [Feature]
useAuth()
useTheme()
useCart()
useNotification()

// Pattern: use + [Action]
useToggle()
useDebounce()
useThrottle()
useInterval()

// Pattern: use + [Data]
useLocalStorage()
useSessionStorage()
useCookie()
useQuery()

File Naming

// hooks/
useAuth.js
useLocalStorage.js
useFetch.js
useDebounce.js

Or grouped:

// hooks/
auth/useAuth.js
storage/useLocalStorage.js
api/useFetch.js

Common Patterns

Common Patterns

useToggle

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = useCallback(() => setValue((prev) => !prev), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);

  return { value, toggle, setTrue, setFalse };
}

// Usage
function Modal() {
  const { value: isOpen, toggle, setFalse: close } = useToggle();

  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      {isOpen && (
        <div className="modal">
          <p>Modal content</p>
          <button onClick={close}>Close</button>
        </div>
      )}
    </div>
  );
}

useDebounce

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchInput() {
  const [search, setSearch] = useState("");
  const debouncedSearch = useDebounce(search, 500);

  useEffect(() => {
    if (debouncedSearch) {
      fetchResults(debouncedSearch);
    }
  }, [debouncedSearch]);

  return <input value={search} onChange={(e) => setSearch(e.target.value)} />;
}

useWindowSize

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;
}

// Usage
function ResponsiveComponent() {
  const { width } = useWindowSize();
  return <div>{width < 768 ? <MobileView /> : <DesktopView />}</div>;
}

usePrevious

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// Usage
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Now: {count}, Before: {prevCount}</p>
    </div>
  );
}

useInterval

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

// Usage
function Timer() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount((c) => c + 1);
  }, 1000);
}

Practice Problems

0/3solved
Build Custom Hooks Component

Create a reusable React component implementing Custom Hooks. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Custom Hooks Testing

Write unit and integration tests for Custom Hooks using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Custom Hooks Performance

Optimize Custom Hooks for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What must custom hook names start with?

Question 1 options

2. Can custom hooks call other hooks?

Question 2 options

3. What is the benefit of custom hooks?

Question 3 options

4. Where should custom hooks be defined?

Question 4 options

Flashcards

Question

What is a custom hook?

Answer

A function that starts with 'use' and can call other hooks

Question

Why use custom hooks?

Answer

To extract and reuse stateful logic

Question

What is the naming convention for custom hooks?

Answer

Start with 'use' and follow camelCase

Question

Can custom hooks return values?

Answer

Yes, they can return any value including arrays, objects, and functions

Question

What is Custom Hooks?

Answer

Custom Hooks is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.Custom hooks start with 'use'
  • 2.They can call other hooks
  • 3.Extract reusable stateful logic
  • 4.Follow naming conventions
  • 5.Return values and setter functions

Interview Tips

  • Show how to create a custom hook
  • Explain when to extract logic into a hook
  • Demonstrate common custom hook patterns

Cheat Sheet

Cheat Sheet

Custom Hook Structure

function useSomething(args) {
  const [state, setState] = useState(initial);
  // ... logic
  return [state, setState];
}

Common Patterns

useLocalStorage(key, initial)
useFetch(url)
useToggle(initial)
useDebounce(value, delay)
useWindowSize()
usePrevious(value)
useInterval(callback, delay)

Rules

  • Start with 'use'
  • Can call other hooks
  • Return values/functions
  • Extract reusable logic