Exploring the Power of Custom React Hooks: 7 Custom Hook Examples

React, a popular JavaScript library for building user interfaces revolutionized front-end development by introducing hooks in version 16.8. Hooks allow developers to use state and other React features without writing a class. One of the most powerful features of hooks is the ability to create custom hooks, enabling the encapsulation and reuse of logic across multiple components.

This article will explore the power of custom React hooks with seven practical examples showcasing their versatility and benefits.

1. useFetch: Simplifying Data Fetching

Fetching data from an API is a common requirement in web applications. The useFetch custom hook abstracts the logic for fetching data, managing loading states, and handling errors.

Implementation

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Error: ${response.statusText}`);
        }
        const result = await response.json();
        setData(result);
        setError(null);
      } catch (err) {
        setError(err.message);
        setData(null);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;

Usage

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

const PostList = () => {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');

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

  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export default PostList;

2. useLocalStorage: Managing Local Storage

Managing local storage can be tedious and error-prone. The useLocalStorage hook simplifies this by providing an easy interface for getting and setting values in local storage.

Implementation

import { useState } from 'react';

const 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 {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
};

export default useLocalStorage;

Usage

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

const Counter = () => {
  const [count, setCount] = useLocalStorage('count', 0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
};

export default Counter;

3. useDebounce: Handling Debounced Values

Debouncing is a technique that limits the rate at which a function is executed. The useDebounce hook helps manage debounced values and is useful for search inputs and other real-time filtering operations.

Implementation

import { useState, useEffect } from 'react';

const 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

import React, { useState } from 'react';
import useDebounce from './useDebounce';

const SearchInput = ({ onSearch }) => {
  const [input, setInput] = useState('');
  const debouncedInput = useDebounce(input, 500);

  useEffect(() => {
    onSearch(debouncedInput);
  }, [debouncedInput, onSearch]);

  return (
    <input
      type="text"
      value={input}
      onChange={(e) => setInput(e.target.value)}
      placeholder="Search..."
    />
  );
};

export default SearchInput;

4. usePrevious: Accessing Previous State

Sometimes you need to access the previous state value. The usePrevious hook allows you to do this easily.

Implementation

import { useEffect, useRef } from 'react';

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

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

  return ref.current;
};

export default usePrevious;

Usage

import React, { useState, useEffect } from 'react';
import usePrevious from './usePrevious';

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

  useEffect(() => {
    if (prevCount !== undefined) {
      console.log(`Previous count: ${prevCount}`);
    }
  }, [count, prevCount]);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;

5. useWindowSize: Tracking Window Size

Responsive design often requires knowing the window size. The useWindowSize hook provides an easy way to track window dimensions.

Implementation

import { useState, useEffect } from 'react';

const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

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

    window.addEventListener('resize', handleResize);

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

  return windowSize;
};

export default useWindowSize;

Usage

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

const WindowSizeComponent = () => {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Width: {width}px</p>
      <p>Height: {height}px</p>
    </div>
  );
};

export default WindowSizeComponent;

6. useOnlineStatus: Detecting Online/Offline Status

Detecting the user’s online or offline status can be useful for building resilient web applications. The useOnlineStatus hook helps track this status.

Implementation

import { useState, useEffect } from 'react';

const useOnlineStatus = () => {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
};

export default useOnlineStatus;
</code>

Usage

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

const OnlineStatusComponent = () => {
  const isOnline = useOnlineStatus();

  return (
    <div>
      <p>You are currently {isOnline ? 'online' : 'offline'}.</p>
    </div>
  );
};

export default OnlineStatusComponent;

7. useHover: Detecting Hover State

Detecting whether an element is being hovered can enhance user interactions. The useHover hook manages the hover state for any element.

Implementation

import { useState, useRef, useEffect } from 'react';

const useHover = () => {
  const [hovered, setHovered] = useState(false);
  const ref = useRef(null);

  const handleMouseOver = () => setHovered(true);
  const handleMouseOut = () => setHovered(false);

  useEffect(() => {
    const node = ref.current;
    if (node) {
      node.addEventListener('mouseover', handleMouseOver);
      node.addEventListener('mouseout', handleMouseOut);

      return () => {
        node.removeEventListener('mouseover', handleMouseOver);
        node.removeEventListener('mouseout', handleMouseOut);
      };
    }
  }, [ref.current]);

  return [ref, hovered];
};

export default useHover;

Usage

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

const HoverComponent = () => {
  const [hoverRef, isHovered] = useHover();

  return (
    <div>
      <div ref={hoverRef} style={{ width: '100px', height: '100px', backgroundColor: isHovered ? 'blue' : 'gray' }}>
        Hover over me!
      </div>
    </div>
  );
};

export default HoverComponent;

Wrapping Up

Custom React hooks offer a powerful way to encapsulate and reuse logic across your components, promoting cleaner and more maintainable code. Abstracting repetitive tasks into custom hooks can enhance your application’s functionality and improve developer productivity.

The seven examples provided—useFetch, useLocalStorage, useDebounce, usePrevious, useWindowSize, useOnlineStatus, and useHover—demonstrate the versatility and utility of custom hooks in various scenarios. As you develop more complex applications, consider leveraging custom hooks to simplify your code and enhance your development workflow.



Leave a Reply

Your email address will not be published. Required fields are marked *