5 min read

Mastering React Hooks: From Basics to Custom Implementation

React Hooks have made functional components incredibly powerful, but taking them to the next level unlocks even more potential. This post dives into advanced useEffect patterns, performance wins with useMemo, and battle-tested custom Hooks that’ll make your code cleaner and more reusable.

Advanced useEffect Patterns

useEffect handles side effects, but the real magic happens when you master cleanup functions and dependency tuning. That return function from useEffect runs right before the next effect or when the component unmountsâperfect for preventing memory leaks.

Take window resize tracking:

import { useState, useEffect } from 'react';
function WindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });

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

window.addEventListener('resize', updateSize);
updateSize(); // Kick it off immediately

return () => window.removeEventListener('resize', updateSize);
}, []); // Empty deps = runs once

return <div>Window size: {size.width} Ã {size.height}</div>;
}

Pro tip: Swap in useLayoutEffect for DOM measurementsâit runs synchronously before paint, no flash.

Performance Boosts with useMemo & useCallback

Child components re-rendering on every parent update? Thatâs where useMemo (for expensive calculations) and useCallback (for stable functions) save the day.

Real-world product filtering:

function ProductList({ products, searchTerm }) {
const filteredProducts = useMemo(() => {
return products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]); // Recalculate only when these change

const handleSort = useCallback((sortBy) => {
// Sorting logic here
}, []);

return (
<div>
{filteredProducts.map(product => (
<Product key={product.id} onSort={handleSort} />
))}
</div>
);
}

Result: With thousands of products, filtering happens once per keystroke instead of every render.

Custom Hooks That Actually Ship

Custom Hooks start with âuseâ, call other Hooks internally, and return reusable logic. Hereâs three Iâve used in production:

1. useDebounce - Tame Search Inputs

import { useState, useEffect } from 'react';

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

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

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

return debouncedValue;
}
// In your search component:
function SearchInput() {
const [input, setInput] = useState('');
const debouncedSearch = useDebounce(input, 500);

useEffect(() => {
if (debouncedSearch) {
fetchProducts(debouncedSearch); // API call
}
}, [debouncedSearch]);

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

2. useToggle - Simple On/Off States

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

const toggle = () => setValue(prev => !prev);
const setTrue = () => setValue(true);
const setFalse = () => setValue(false);

return [value, { toggle, setTrue, setFalse }];
}
// Usage: const [isOpen, { toggle }] = useToggle(false);

3. useLocalStorage - Persist State Effortlessly

function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});

const setStoredValue = (newValue) => {
try {
setValue(newValue);
window.localStorage.setItem(key, JSON.stringify(newValue));
} catch (error) {
console.error(error);
}
};

return [value, setStoredValue];
}

Pitfalls and Hard-Won Lessons

  • Functional updates: Always  setCount(prev => prev + 1)  to grab the latest state, dodging stale closures.

  • Full deps: Let  eslint-plugin-react-hooks  catch missing dependencies.

  • Complex state: Reach for  useReducer  over multiple  useState  calls.

  • The rules: Top-level only. No conditionals or loops.

Scenario

Hook Combo

Win

Search input

useDebounce + useEffect

80% fewer API calls

Large lists

useMemo + useCallback

3x render speedup

Forms

useReducer + useCallback

Predictable state

Persistence

useLocalStorage

Instant sync

These patterns have cleaned up my e-commerce apps big time. Drop your favorite custom Hooks in the comments!

Comments (0)

No comments yet. Be the first to share your thoughts!