React JS - Custom Hooks

Vitthal KorvanVitthal Korvan
4 min read

Custom Hooks

1. What are Custom Hooks?

Custom hooks are essentially JavaScript functions that use other React hooks (like useState, useEffect, useContext, etc.) to encapsulate reusable logic. They allow you to extract component logic into reusable functions and maintain stateful logic independently of the component’s UI.

  • Why use custom hooks?

    • To reuse logic across components without duplicating code.

    • To abstract complex logic into smaller, maintainable pieces.

    • To keep components clean and focused on rendering UI instead of handling logic.

2. Creating a Custom Hook

A custom hook is just a function that starts with the prefix use and can call other hooks inside of it. The naming convention is important because React relies on this prefix to identify hooks and apply rules of hooks (like only calling hooks at the top level and within React components or other custom hooks).

Example: useWindowWidth Custom Hook

import { useState, useEffect } from 'react';

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);

    window.addEventListener('resize', handleResize);

    // Cleanup on unmount
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

export default useWindowWidth;

3. Using a Custom Hook

You can use a custom hook in any component just like you would use a built-in React hook.

import React from 'react';
import useWindowWidth from './useWindowWidth';

function Component() {
  const width = useWindowWidth();

  return (
    <div>
      <p>The window width is: {width}</p>
    </div>
  );
}

4. Benefits of Custom Hooks

  • Reusability: Logic can be reused across multiple components.

  • Separation of Concerns: Components are focused on UI, while the custom hooks handle logic.

  • Cleaner Components: Extracting logic into hooks keeps components simpler and more readable.

  • Share Stateful Logic: Custom hooks can share state and behavior between multiple components.

5. Common Use Cases for Custom Hooks

  1. Fetching Data (with side effects)

    Custom Hook Example 1 - useFetch Hook

     import { useState, useEffect } from 'react';
    
     function useFetch(url) {
       const [data, setData] = useState(null);
       const [loading, setLoading] = useState(true);
       const [error, setError] = useState(null);
    
       useEffect(() => {
         async function fetchData() {
           try {
             const response = await fetch(url);
             const result = await response.json();
             setData(result);
           } catch (err) {
             setError(err);
           } finally {
             setLoading(false);
           }
         }
         fetchData();
       }, [url]);
    
       return { data, loading, error };
     }
    
     export default useFetch;
    

    Usage:

     const { data, loading, error } = useFetch('https://api.example.com/data');
    
  2. Managing Form State

    Example: useFormInput

     import { useState } from 'react';
    
     function useFormInput(initialValue) {
       const [value, setValue] = useState(initialValue);
    
       const handleChange = (e) => {
         setValue(e.target.value);
       };
    
       return {
         value,
         onChange: handleChange,
       };
     }
    
     export default useFormInput;
    

    Usage:

     function MyForm() {
       const name = useFormInput('');
       const email = useFormInput('');
    
       return (
         <form>
           <input type="text" placeholder="Name" {...name} />
           <input type="email" placeholder="Email" {...email} />
         </form>
       );
     }
    
  3. Debouncing Values (e.g., for search inputs)

    Example: useDebounce

     import { useState, useEffect } from 'react';
    
     function useDebounce(value, delay) {
       const [debouncedValue, setDebouncedValue] = useState(value);
    
       useEffect(() => {
         const handler = setTimeout(() => {
           setDebouncedValue(value);
         }, delay);
    
         return () => {
           clearTimeout(handler);
         };
       }, [value, delay]);
    
       return debouncedValue;
     }
    
     export default useDebounce;
    

    Usage:

     const searchTerm = useDebounce(inputValue, 500);
    
  4. Using Local Storage

    Example: useLocalStorage

     import { useState } from 'react';
    
     function useLocalStorage(key, initialValue) {
       const [storedValue, setStoredValue] = useState(() => {
         try {
           const item = window.localStorage.getItem(key);
           return item ? JSON.parse(item) : initialValue;
         } catch (error) {
           console.log(error);
           return initialValue;
         }
       });
    
       const setValue = (value) => {
         try {
           setStoredValue(value);
           window.localStorage.setItem(key, JSON.stringify(value));
         } catch (error) {
           console.log(error);
         }
       };
    
       return [storedValue, setValue];
     }
    
     export default useLocalStorage;
    

    Usage:

     const [name, setName] = useLocalStorage('name', 'John');
    

6. Rules of Hooks

When creating custom hooks, the same rules of hooks that apply to built-in React hooks also apply:

  1. Only call hooks at the top level: Don't call hooks inside loops, conditions, or nested functions.

  2. Only call hooks from React functions: Hooks should only be called in functional components or within other custom hooks.

7. Custom Hooks with Parameters

Custom hooks can accept arguments to customize their behavior.

For example, modifying the useFetch hook to take a dependency array:

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

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }
    fetchData();
  }, deps); // Notice the deps array

  return { data, loading, error };
}

8. Advanced Example: Combining Multiple Hooks

Sometimes you need more advanced logic that involves multiple hooks. Here’s an example of using both useState and useEffect within a custom hook:

Example: useDocumentTitle Hook

import { useState, useEffect } from 'react';

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount((prev) => prev + 1);
  const decrement = () => setCount((prev) => prev - 1);

  return { count, increment, decrement };
}

export default function CounterComponent() {
  const { count, increment, decrement } = useCounter(5);
  useDocumentTitle(`Counter: ${count}`);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
4
Subscribe to my newsletter

Read articles from Vitthal Korvan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vitthal Korvan
Vitthal Korvan

🚀 Hello, World! I'm Vitthal Korvan 🚀 As a passionate front-end web developer, I transform digital landscapes into captivating experiences. you'll find me exploring the intersection of technology and art, sipping on a cup of coffee, or contributing to the open-source community. Life is an adventure, and I bring that spirit to everything I do.