Code Snippets
A curated collection of useful code snippets, utilities, and helper functions that I use frequently in my projects. Free to use and contribute to.
Most Popular
useLocalStorage Hook
A custom React hook for managing localStorage with automatic JSON serialization and type safety.
import { useState, useEffect } from 'react';
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
// Get from local storage then parse stored...
Debounce Function
A utility function to debounce expensive operations like API calls or search inputs.
function debounce(func, delay) {
let timeoutId;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeoutId);
func.apply(this, args);
};
...
API Error Handler
A robust error handling utility for API requests with retry logic and proper error typing.
interface ApiError {
message: string;
status: number;
code?: string;
}
interface RetryConfig {
maxRetries: number;
delay: number;
backoff: boolean;
}
class ApiErrorHandler {
private st...
All Snippets (6)
useLocalStorage Hook
A custom React hook for managing localStorage with automatic JSON serialization and type safety.
import { useState, useEffect } from 'react';
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
// Get from local storage then parse stored json or return initialValue
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that persists the new value to localStorage
const setValue = (value: T | ((val: T) => T)) => {
try {
// Allow value to be a function so we have the same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
return [storedValue, setValue];
}
// Usage example:
function App() {
const [name, setName] = useLocalStorage('name', 'Anonymous');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
Debounce Function
A utility function to debounce expensive operations like API calls or search inputs.
function debounce(func, delay) {
let timeoutId;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeoutId);
func.apply(this, args);
};
clearTimeout(timeoutId);
timeoutId = setTimeout(later, delay);
};
}
// TypeScript version with proper typing
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return function executedFunction(...args: Parameters<T>) {
const later = () => {
clearTimeout(timeoutId);
func.apply(this, args);
};
clearTimeout(timeoutId);
timeoutId = setTimeout(later, delay);
};
}
// Usage examples:
const searchAPI = debounce((query) => {
fetch(`/api/search?q=${query}`)
.then(response => response.json())
.then(data => console.log(data));
}, 300);
// In React component:
const handleSearch = debounce((value) => {
setSearchResults(performSearch(value));
}, 500);
CSS Grid Auto-Fit Cards
Responsive card layout using CSS Grid that automatically adjusts the number of columns based on available space.
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
padding: 1rem;
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
/* For smaller cards */
.cards-container--small {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
/* For larger cards */
.cards-container--large {
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
/* Responsive breakpoints */
@media (max-width: 768px) {
.cards-container {
grid-template-columns: 1fr;
padding: 0.5rem;
}
}
API Error Handler
A robust error handling utility for API requests with retry logic and proper error typing.
interface ApiError {
message: string;
status: number;
code?: string;
}
interface RetryConfig {
maxRetries: number;
delay: number;
backoff: boolean;
}
class ApiErrorHandler {
private static defaultRetryConfig: RetryConfig = {
maxRetries: 3,
delay: 1000,
backoff: true,
};
static async withRetry<T>(
apiCall: () => Promise<T>,
config: Partial<RetryConfig> = {}
): Promise<T> {
const { maxRetries, delay, backoff } = {
...this.defaultRetryConfig,
...config,
};
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await apiCall();
} catch (error) {
lastError = error as Error;
if (attempt === maxRetries) {
throw this.formatError(error);
}
const waitTime = backoff ? delay * Math.pow(2, attempt) : delay;
await this.sleep(waitTime);
}
}
throw this.formatError(lastError!);
}
private static formatError(error: any): ApiError {
if (error.response) {
return {
message: error.response.data?.message || 'API request failed',
status: error.response.status,
code: error.response.data?.code,
};
}
return {
message: error.message || 'Network error occurred',
status: 0,
};
}
private static sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage example:
async function fetchUserData(userId: string) {
try {
const userData = await ApiErrorHandler.withRetry(
() => fetch(`/api/users/${userId}`).then(res => res.json()),
{ maxRetries: 2, delay: 500 }
);
return userData;
} catch (error) {
const apiError = error as ApiError;
console.error(`Failed to fetch user: ${apiError.message}`);
throw error;
}
}
React Form Validation Hook
A flexible form validation hook with support for custom rules and real-time validation.
import { useState, useMemo } from 'react';
type ValidationRule<T> = {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: T) => string | undefined;
};
type ValidationRules<T> = {
[K in keyof T]?: ValidationRule<T[K]>;
};
type ValidationErrors<T> = {
[K in keyof T]?: string;
};
function useFormValidation<T extends Record<string, any>>(
initialValues: T,
rules: ValidationRules<T>
) {
const [values, setValues] = useState<T>(initialValues);
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
const errors = useMemo(() => {
const newErrors: ValidationErrors<T> = {};
Object.keys(rules).forEach((key) => {
const field = key as keyof T;
const rule = rules[field];
const value = values[field];
if (!rule) return;
if (rule.required && (!value || value === '')) {
newErrors[field] = `${String(field)} is required`;
return;
}
if (value && rule.minLength && String(value).length < rule.minLength) {
newErrors[field] = `${String(field)} must be at least ${rule.minLength} characters`;
return;
}
if (value && rule.maxLength && String(value).length > rule.maxLength) {
newErrors[field] = `${String(field)} must be no more than ${rule.maxLength} characters`;
return;
}
if (value && rule.pattern && !rule.pattern.test(String(value))) {
newErrors[field] = `${String(field)} format is invalid`;
return;
}
if (rule.custom) {
const customError = rule.custom(value);
if (customError) {
newErrors[field] = customError;
}
}
});
return newErrors;
}, [values, rules]);
const updateValue = (field: keyof T, value: T[keyof T]) => {
setValues(prev => ({ ...prev, [field]: value }));
};
const updateTouched = (field: keyof T) => {
setTouched(prev => ({ ...prev, [field]: true }));
};
const isValid = Object.keys(errors).length === 0;
const hasErrors = (field: keyof T) => touched[field] && errors[field];
return {
values,
errors,
touched,
isValid,
updateValue,
updateTouched,
hasErrors,
reset: () => {
setValues(initialValues);
setTouched({});
},
};
}
// Usage example:
function LoginForm() {
const { values, errors, updateValue, updateTouched, hasErrors, isValid } =
useFormValidation(
{ email: '', password: '' },
{
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
},
password: {
required: true,
minLength: 8,
custom: (value) => {
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return 'Password must contain uppercase, lowercase and number';
}
},
},
}
);
return (
<form>
<input
type="email"
value={values.email}
onChange={(e) => updateValue('email', e.target.value)}
onBlur={() => updateTouched('email')}
/>
{hasErrors('email') && <span>{errors.email}</span>}
<input
type="password"
value={values.password}
onChange={(e) => updateValue('password', e.target.value)}
onBlur={() => updateTouched('password')}
/>
{hasErrors('password') && <span>{errors.password}</span>}
<button type="submit" disabled={!isValid}>
Login
</button>
</form>
);
}
Intersection Observer Hook
A React hook for detecting when elements enter or leave the viewport, perfect for lazy loading and animations.
import { useEffect, useRef, useState } from 'react';
interface UseIntersectionObserverProps {
threshold?: number | number[];
root?: Element | null;
rootMargin?: string;
freezeOnceVisible?: boolean;
}
function useIntersectionObserver({
threshold = 0,
root = null,
rootMargin = '0%',
freezeOnceVisible = false,
}: UseIntersectionObserverProps = {}) {
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const [isVisible, setIsVisible] = useState(false);
const elementRef = useRef<Element>();
const frozen = entry?.isIntersecting && freezeOnceVisible;
const updateEntry = ([entry]: IntersectionObserverEntry[]) => {
setEntry(entry);
setIsVisible(entry.isIntersecting);
};
useEffect(() => {
const node = elementRef?.current;
const hasIOSupport = !!window.IntersectionObserver;
if (!hasIOSupport || frozen || !node) return;
const observerParams = { threshold, root, rootMargin };
const observer = new IntersectionObserver(updateEntry, observerParams);
observer.observe(node);
return () => observer.disconnect();
}, [elementRef.current, JSON.stringify(threshold), root, rootMargin, frozen]);
return { elementRef, entry, isVisible };
}
// Usage examples:
// 1. Lazy loading images
function LazyImage({ src, alt }: { src: string; alt: string }) {
const { elementRef, isVisible } = useIntersectionObserver({
threshold: 0.1,
freezeOnceVisible: true,
});
return (
<div ref={elementRef as React.RefObject<HTMLDivElement>}>
{isVisible ? (
<img src={src} alt={alt} />
) : (
<div className="placeholder">Loading...</div>
)}
</div>
);
}
// 2. Fade in animation
function FadeInSection({ children }: { children: React.ReactNode }) {
const { elementRef, isVisible } = useIntersectionObserver({
threshold: 0.3,
freezeOnceVisible: true,
});
return (
<div
ref={elementRef as React.RefObject<HTMLDivElement>}
className={`transition-opacity duration-700 ${
isVisible ? 'opacity-100' : 'opacity-0'
}`}
>
{children}
</div>
);
}
// 3. Infinite scroll
function useInfiniteScroll(loadMore: () => void) {
const { elementRef, isVisible } = useIntersectionObserver({
threshold: 1.0,
});
useEffect(() => {
if (isVisible) {
loadMore();
}
}, [isVisible, loadMore]);
return elementRef;
}
Contribute
Have a useful code snippet to share? Contribute to the collection and help other developers solve common problems.