React has revolutionized how developers build user interfaces. One of its standout features is Hooks, introduced in React 16.8. While built-in hooks like useState and useEffect are staples in most applications, custom hooks allow developers to abstract and reuse logic across components with elegance and clarity.
In this blog, we'll explore unique and practical use cases for custom hooks, complete with real-world examples that go beyond the common “form validation” or “fetch data” use cases.
What is a Custom Hook?
A custom hook is a special JavaScript function that starts with the word "use" and can include other hooks. It helps you organize and reuse shared logic across different components in your app.
// Basic structure
function useMyHook() {
const [state, setState] = useState(null);
// logic here...
return state;
}
1. usePrevious : Track Previous Value of a Prop or State
Sometimes you need to know what the previous value of a state or prop was — for example, to detect if a value has changed meaningfully.
Code Example:
import { useRef, useEffect } from 'react';
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Usage:
const MyComponent = ({ count }) => {
const prevCount = usePrevious(count);
useEffect(() => {
if (prevCount !== undefined && count > prevCount) {
console.log('Count increased!');
}
}, [count]);
return <div>Count: {count}</div>;
};
2. useInterval : Set Up Declarative Intervals
Timers can be tricky in React, especially when working with state. This custom hook makes it easier to handle intervals declaratively.
import { useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}
}, [delay]);
}
Usage:
const Timer = () => {
const [seconds, setSeconds] = useState(0);
useInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return <div>{seconds} seconds elapsed.</div>;
};
3. useDarkMode: Sync Theme with LocalStorage & System Preferences
Want to provide a dark mode toggle that persists and respects system settings? Here's a clever hook.
Code Example:
function useDarkMode() {
const [enabled, setEnabled] = useState(() => {
const saved = localStorage.getItem('dark-mode');
return saved !== null ? JSON.parse(saved) : window.matchMedia('(prefers-color-scheme: dark)').matches;
});
useEffect(() => {
localStorage.setItem('dark-mode', JSON.stringify(enabled));
document.body.classList.toggle('dark-mode', enabled);
}, [enabled]);
return [enabled, setEnabled];
}
Usage:
const DarkModeToggle = () => {
const [darkMode, setDarkMode] = useDarkMode();
return (
<button onClick={() => setDarkMode(prev => !prev)}>
{darkMode ? 'Disable' : 'Enable'} Dark Mode
</button>
);
};
4. useUndoRedo: Implement Undo/Redo for Complex State
This hook is useful for drawing apps, text editors, or any scenario where the user might want to undo or redo changes.
Code Example:
function useUndoRedo(initialValue) {
const [past, setPast] = useState([]);
const [present, setPresent] = useState(initialValue);
const [future, setFuture] = useState([]);
const set = (newPresent) => {
setPast([...past, present]);
setPresent(newPresent);
setFuture([]);
};
const undo = () => {
if (past.length === 0) return;
const previous = past[past.length - 1];
const newPast = past.slice(0, -1);
setPast(newPast);
setFuture([present, ...future]);
setPresent(previous);
};
const redo = () => {
if (future.length === 0) return;
const next = future[0];
const newFuture = future.slice(1);
setPast([...past, present]);
setPresent(next);
setFuture(newFuture);
};
return { present, set, undo, redo, canUndo: past.length > 0, canRedo: future.length > 0 };
}
Usage:
const UndoableInput = () => {
const { present, set, undo, redo, canUndo, canRedo } = useUndoRedo("");
return (
<div>
<input value={present} onChange={e => set(e.target.value)} />
<button onClick={undo} disabled={!canUndo}>Undo</button>
<button onClick={redo} disabled={!canRedo}>Redo</button>
</div>
);
};
5. useNetworkStatus: Detect Online/Offline Status
This custom hook can help you inform users about their connectivity status or trigger background syncs.
Code Example:
import { useState, useEffect } from 'react';
function useNetworkStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setOnline(true);
const handleOffline = () => setOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return online;
}
Usage:
const StatusBanner = () => {
const isOnline = useNetworkStatus();
return <div>{isOnline ? '🟢 You are online' : '🔴 You are offline'}</div>;
};
Final Thoughts Custom hooks are more than just tools for abstraction — they’re a way to rethink how you build your components. From handling timers and theme preferences to enabling undo/redo functionality, custom hooks make your components cleaner, smarter, and more maintainable.
Next time you're repeating logic across multiple components or want to encapsulate a specific behavior, consider writing a custom hook. You might just end up creating something you’ll reuse in every project.
— Sudhir Yadav, Senior Software Engineer