function Counter() {
const [count, setCount] = useState(0);
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<h2>{count}</h2>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(c => c - 1)} style={{ margin: '0 10px' }}>−1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
function UpperInput() {
const [text, setText] = useState('');
return (
<input
type="text"
value={text}
onChange={e => setText(e.target.value.toUpperCase())}
placeholder="Type anything..."
style={{ padding: '8px', width: '300px' }}
/>
);
}
function UserFetcher({ id = 1 }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(setUser)
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [id]);
if (loading) return <p>Loading user...</p>;
if (error) return <p style={{color:'red'}}>Error: {error}</p>;
if (!user) return null;
return (
<div>
<h3>{user.name}</h3>
<p>@{user.username} • {user.email}</p>
<p>{user.company.name} — {user.company.catchPhrase}</p>
</div>
);
}
function Toggle() {
const [on, setOn] = useState(false);
return (
<label style={{ display: 'inline-flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={on}
onChange={() => setOn(v => !v)}
style={{ display: 'none' }}
/>
<span
style={{
width: '50px',
height: '26px',
background: on ? '#2196f3' : '#ccc',
borderRadius: '13px',
position: 'relative',
transition: 'background 0.25s'
}}
>
<span
style={{
position: 'absolute',
width: '22px',
height: '22px',
background: 'white',
borderRadius: '50%',
top: '2px',
left: on ? '24px' : '2px',
transition: 'left 0.25s'
}}
/>
</span>
<span style={{ marginLeft: '12px' }}>{on ? 'ON' : 'OFF'}</span>
</label>
);
}
function SignupForm() {
const [values, setValues] = useState({ name: '', email: '', password: '' });
const [errors, setErrors] = useState({});
const handleChange = e => {
setValues({ ...values, [e.target.name]: e.target.value });
};
const validate = () => {
const errs = {};
if (!values.name.trim()) errs.name = "Name is required";
if (!/\S+@\S+\.\S+/.test(values.email)) errs.email = "Valid email required";
if (values.password.length < 6) errs.password = "≥ 6 characters";
return errs;
};
const handleSubmit = e => {
e.preventDefault();
const validation = validate();
setErrors(validation);
if (Object.keys(validation).length === 0) {
alert("Form submitted:\n" + JSON.stringify(values, null, 2));
}
};
return (
<form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
<div>
<input name="name" placeholder="Name" value={values.name} onChange={handleChange} />
{errors.name && <span style={{color:'red', fontSize:'0.9em'}}>{errors.name}</span>}
</div>
<div>
<input name="email" type="email" placeholder="Email" value={values.email} onChange={handleChange} />
{errors.email && <span style={{color:'red', fontSize:'0.9em'}}>{errors.email}</span>}
</div>
<div>
<input name="password" type="password" placeholder="Password" value={values.password} onChange={handleChange} />
{errors.password && <span style={{color:'red', fontSize:'0.9em'}}>{errors.password}</span>}
</div>
<button type="submit">Sign Up</button>
</form>
);
}
function FilterList({ items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig'] }) {
const [query, setQuery] = useState('');
const filtered = useMemo(() => {
if (!query.trim()) return items;
const q = query.toLowerCase();
return items.filter(item => item.toLowerCase().includes(q));
}, [items, query]);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search fruits..."
style={{ width: '100%', padding: '8px', marginBottom: '12px' }}
/>
<ul>
{filtered.map((item, i) => <li key={i}>{item}</li>)}
{!filtered.length && query && <li style={{color:'gray'}}>No matches</li>}
</ul>
</div>
);
}
function useLocalStorage(key, defaultValue) {
const [stored, setStored] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch {
return defaultValue;
}
});
const setValue = value => {
try {
const toStore = value instanceof Function ? value(stored) : value;
setStored(toStore);
localStorage.setItem(key, JSON.stringify(toStore));
} catch (err) {
console.error("localStorage write failed:", err);
}
};
return [stored, setValue];
}
// Usage:
// const [theme, setTheme] = useLocalStorage('theme', 'light');
function LiveClock() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
return (
<h2 style={{ fontFamily: 'monospace', textAlign: 'center' }}>
{now.toLocaleTimeString()}
</h2>
);
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Example usage inside component:
function Example({ count }) {
const prev = usePrevious(count);
return (
<p>
Current: {count} — Previous: {prev ?? '—'}
</p>
);
}
function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState(-1);
return (
<div>
{items.map((item, i) => (
<div key={i}>
<button
onClick={() => setOpenIndex(openIndex === i ? -1 : i)}
style={{ width: '100%', padding: '12px', textAlign: 'left' }}
>
{item.title} {openIndex === i ? '▲' : '▼'}
</button>
{openIndex === i && (
<div style={{ padding: '12px', background: '#f8f9fa' }}>
{item.content}
</div>
)}
</div>
))}
</div>
);
}
// Usage: <Accordion items={[{title:'Q1', content:'A1'}, ...]} />
function Tabs({ tabs }) {
const [active, setActive] = useState(0);
return (
<div>
<div style={{ display: 'flex', borderBottom: '1px solid #ccc' }}>
{tabs.map((tab, i) => (
<button
key={i}
onClick={() => setActive(i)}
style={{
padding: '12px 24px',
border: 'none',
background: active === i ? '#007bff' : 'transparent',
color: active === i ? 'white' : '#333',
cursor: 'pointer'
}}
>
{tab.label}
</button>
))}
</div>
<div style={{ padding: '20px' }}>
{tabs[active].content}
</div>
</div>
);
}
function InfiniteScrollList() {
const [items, setItems] = useState(Array.from({length: 20}, (_, i) => `Item ${i + 1}`));
const [page, setPage] = useState(1);
const loader = useRef(null);
const loadMore = useCallback(() => {
setTimeout(() => {
const newItems = Array.from({length: 10}, (_, i) => `Item ${items.length + i + 1}`);
setItems(prev => [...prev, ...newItems]);
setPage(p => p + 1);
}, 800);
}, [items.length]);
useEffect(() => {
const observer = new IntersectionObserver(
entries => { if (entries[0].isIntersecting) loadMore(); },
{ threshold: 1.0 }
);
if (loader.current) observer.observe(loader.current);
return () => observer.disconnect();
}, [loadMore]);
return (
<div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map((item, i) => (
<li key={i} style={{ padding: '16px', borderBottom: '1px solid #eee' }}>{item}</li>
))}
</ul>
<div ref={loader} style={{ height: '40px', textAlign: 'center', padding: '20px' }}>
Loading more...
</div>
</div>
);
}
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// Usage example:
function Search() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 600);
useEffect(() => {
if (debouncedQuery) console.log('Search API call with:', debouncedQuery);
}, [debouncedQuery]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
useEffect(() => {
if (!isOpen) return;
const handleEsc = e => { if (e.key === 'Escape') onClose(); };
document.addEventListener('keydown', handleEsc);
modalRef.current?.focus();
return () => document.removeEventListener('keydown', handleEsc);
}, [isOpen, onClose]);
if (!isOpen) return null;
return createPortal(
<div
style={{
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}
onClick={onClose}
>
<div
ref={modalRef}
tabIndex={-1}
onClick={e => e.stopPropagation()}
style={{
background: 'white', padding: '24px', borderRadius: '8px',
maxWidth: '500px', width: '90%', outline: 'none'
}}
>
{children}
<button onClick={onClose} style={{ marginTop: '16px' }}>Close</button>
</div>
</div>,
document.body
);
}
// Usage: <Modal isOpen={show} onClose={() => setShow(false)}>Content</Modal>
function DraggableList() {
const [items, setItems] = useState(['Task 1', 'Task 2', 'Task 3', 'Task 4']);
const [dragIndex, setDragIndex] = useState(null);
const handleDragStart = (e, index) => {
setDragIndex(index);
e.dataTransfer.effectAllowed = 'move';
};
const handleDragOver = e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e, dropIndex) => {
e.preventDefault();
if (dragIndex === null || dragIndex === dropIndex) return;
const newItems = [...items];
const [draggedItem] = newItems.splice(dragIndex, 1);
newItems.splice(dropIndex, 0, draggedItem);
setItems(newItems);
setDragIndex(null);
};
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map((item, i) => (
<li
key={i}
draggable
onDragStart={e => handleDragStart(e, i)}
onDragOver={handleDragOver}
onDrop={e => handleDrop(e, i)}
style={{
padding: '12px',
margin: '8px 0',
background: dragIndex === i ? '#e0f7ff' : '#f8f9fa',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'grab'
}}
>
{item}
</li>
))}
</ul>
);
}
function StarRating({ max = 5, onRate }) {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(0);
return (
<div style={{ display: 'flex', gap: '4px' }}>
{Array.from({ length: max }, (_, i) => {
const starValue = i + 1;
return (
<span
key={i}
onClick={() => {
setRating(starValue);
onRate?.(starValue);
}}
onMouseEnter={() => setHover(starValue)}
onMouseLeave={() => setHover(0)}
style={{
fontSize: '28px',
color: starValue <= (hover || rating) ? '#ffc107' : '#e4e5e9',
cursor: 'pointer'
}}
>
★
</span>
);
})}
</div>
);
}
// Usage: <StarRating onRate={rating => console.log(rating)} />
function ImagePreview() {
const [preview, setPreview] = useState(null);
const handleFile = e => {
const file = e.target.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
setPreview(url);
return () => URL.revokeObjectURL(url); // cleanup
};
return (
<div>
<input type="file" accept="image/*" onChange={handleFile} />
{preview && (
<img
src={preview}
alt="Preview"
style={{ maxWidth: '300px', marginTop: '16px', borderRadius: '8px' }}
/>
)}
</div>
);
}
function useDebounce(value, delay = 500) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
function SearchWithDebounce() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 600);
useEffect(() => {
if (!debouncedQuery) return;
console.log('API call → search for:', debouncedQuery);
// fetch(`/api/search?q=${debouncedQuery}`).then(...)
}, [debouncedQuery]);
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search products..."
style={{ width: '100%', padding: '12px' }}
/>
);
}
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(url, { signal: controller.signal })
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') setError(err.message);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Usage:
// const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts/1');
function useClickOutside(ref, handler) {
useEffect(() => {
const listener = event => {
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}
// Usage example:
function Dropdown() {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useClickOutside(ref, () => setOpen(false));
return (
<div ref={ref} style={{ position: 'relative' }}>
<button onClick={() => setOpen(!open)}>Menu</button>
{open && (
<div style={{ position: 'absolute', background: 'white', border: '1px solid #ccc', padding: '12px' }}>
<p>Option 1</p>
<p>Option 2</p>
</div>
)}
</div>
);
}
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// Usage:
// const { width, height } = useWindowSize();
function CopyButton({ text }) {
const [copied, setCopied] = useState(false);
const copy = async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Clipboard failed', err);
// fallback
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
return (
<button onClick={copy} style={{ padding: '8px 16px' }}>
{copied ? 'Copied!' : 'Copy'}
</button>
);
}
function ThemeToggle() {
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme') || 'light';
});
useEffect(() => {
document.documentElement.className = theme;
localStorage.setItem('theme', theme);
}, [theme]);
const toggle = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<button onClick={toggle} style={{ padding: '10px 20px' }}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
}
// Add to index.html or root layout:
// <html class="light"> (initial)
function DynamicForm() {
const [fields, setFields] = useState([{ id: 1, value: '' }]);
const addField = () => {
setFields([...fields, { id: Date.now(), value: '' }]);
};
const removeField = id => {
if (fields.length === 1) return;
setFields(fields.filter(f => f.id !== id));
};
const updateField = (id, value) => {
setFields(fields.map(f => f.id === id ? { ...f, value } : f));
};
return (
<div>
{fields.map(field => (
<div key={field.id} style={{ display: 'flex', marginBottom: '12px' }}>
<input
value={field.value}
onChange={e => updateField(field.id, e.target.value)}
placeholder="Enter value"
style={{ flex: 1, marginRight: '8px' }}
/>
<button onClick={() => removeField(field.id)} style={{ color: 'red' }}>Remove</button>
</div>
))}
<button onClick={addField}>Add Field</button>
</div>
);
}
function Carousel({ images }) {
const [index, setIndex] = useState(0);
const next = () => setIndex(i => (i + 1) % images.length);
const prev = () => setIndex(i => (i - 1 + images.length) % images.length);
return (
<div style={{ position: 'relative', maxWidth: '600px', margin: 'auto' }}>
<img
src={images[index]}
alt={`Slide ${index + 1}`}
style={{ width: '100%', height: '400px', objectFit: 'cover', borderRadius: '8px' }}
/>
<button
onClick={prev}
style={{ position: 'absolute', left: '10px', top: '50%', transform: 'translateY(-50%)' }}
>←</button>
<button
onClick={next}
style={{ position: 'absolute', right: '10px', top: '50%', transform: 'translateY(-50%)' }}
>→</button>
<div style={{ textAlign: 'center', marginTop: '12px' }}>
{index + 1} / {images.length}
</div>
</div>
);
}
// Usage: <Carousel images={['img1.jpg', 'img2.jpg', ...]} />
function Countdown({ initial = 60 }) {
const [seconds, setSeconds] = useState(initial);
useEffect(() => {
if (seconds <= 0) return;
const id = setInterval(() => {
setSeconds(s => s - 1);
}, 1000);
return () => clearInterval(id);
}, [seconds]);
return (
<div style={{ fontSize: '3rem', textAlign: 'center' }}>
{seconds === 0 ? 'Time’s up!' : seconds}
<button onClick={() => setSeconds(initial)} style={{ marginLeft: '20px', fontSize: '1rem' }}>
Restart
</button>
</div>
);
}
function Typeahead({ suggestions = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'] }) {
const [input, setInput] = useState('');
const [show, setShow] = useState(false);
const filtered = suggestions.filter(s =>
s.toLowerCase().startsWith(input.toLowerCase())
);
return (
<div style={{ position: 'relative', width: '300px' }}>
<input
value={input}
onChange={e => { setInput(e.target.value); setShow(true); }}
onFocus={() => setShow(true)}
onBlur={() => setTimeout(() => setShow(false), 100)}
placeholder="Search fruit..."
/>
{show && filtered.length > 0 && (
<ul style={{
position: 'absolute',
width: '100%',
maxHeight: '200px',
overflowY: 'auto',
background: 'white',
border: '1px solid #ccc',
margin: 0,
padding: 0,
listStyle: 'none',
zIndex: 10
}}>
{filtered.map((item, i) => (
<li
key={i}
onClick={() => { setInput(item); setShow(false); }}
style={{ padding: '8px', cursor: 'pointer' }}
onMouseDown={e => e.preventDefault()}
>
{item}
</li>
))}
</ul>
)}
</div>
);
}
function VirtualList({ items, itemHeight = 50, height = 400 }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);
const visibleCount = Math.ceil(height / itemHeight) + 2; // overscan
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - 1);
const visibleItems = items.slice(startIndex, startIndex + visibleCount);
return (
<div
ref={containerRef}
onScroll={e => setScrollTop(e.target.scrollTop)}
style={{ height: `${height}px`, overflowY: 'auto', position: 'relative' }}
>
<div style={{ height: `${items.length * itemHeight}px`, position: 'relative' }}>
{visibleItems.map((item, i) => (
<div
key={startIndex + i}
style={{
position: 'absolute',
top: `${(startIndex + i) * itemHeight}px`,
height: `${itemHeight}px`,
width: '100%',
padding: '12px',
boxSizing: 'border-box',
borderBottom: '1px solid #eee'
}}
>
{item}
</div>
))}
</div>
</div>
);
}
// Usage: <VirtualList items={Array(1000).fill().map((_,i)=>`Row ${i+1}`)} />
const initialForm = { name: '', age: '', email: '', agree: false };
function formReducer(state, action) {
switch (action.type) {
case 'update':
return { ...state, [action.field]: action.value };
case 'reset':
return initialForm;
default:
return state;
}
}
function ComplexForm() {
const [form, dispatch] = useReducer(formReducer, initialForm);
const isValid = form.name && form.age > 0 && form.email.includes('@') && form.agree;
return (
<form style={{ maxWidth: '400px' }}>
<input
placeholder="Name"
value={form.name}
onChange={e => dispatch({ type: 'update', field: 'name', value: e.target.value })}
/>
<input
type="number"
placeholder="Age"
value={form.age}
onChange={e => dispatch({ type: 'update', field: 'age', value: Number(e.target.value) })}
/>
<input
placeholder="Email"
value={form.email}
onChange={e => dispatch({ type: 'update', field: 'email', value: e.target.value })}
/>
<label>
<input
type="checkbox"
checked={form.agree}
onChange={e => dispatch({ type: 'update', field: 'agree', value: e.target.checked })}
/>
I agree to terms
</label>
<button disabled={!isValid} onClick={() => alert('Submitted!')}>
Submit
</button>
<button type="button" onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</form>
);
}
function LikeButton({ initialLikes = 42 }) {
const [likes, setLikes] = useState(initialLikes);
const [optimistic, setOptimistic] = useState(false);
const [error, setError] = useState(null);
const handleLike = async () => {
const previous = likes;
setOptimistic(true);
setLikes(likes + 1);
try {
// simulate API call
await new Promise((res, rej) => setTimeout(() => rej(new Error('Failed')), 1200));
// await api.likePost();
} catch (err) {
setError('Failed to like');
setLikes(previous);
} finally {
setOptimistic(false);
}
};
return (
<button
onClick={handleLike}
disabled={optimistic}
style={{ padding: '8px 16px', background: optimistic ? '#ccc' : '#ff4081' }}
>
❤️ {likes}
{optimistic && ' (liking...)'}
{error && <span style={{ color: 'red', marginLeft: '8px' }}>{error}</span>}
</button>
);
}
function HoverCard() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [visible, setVisible] = useState(false);
const handleMouseMove = e => {
setPosition({ x: e.clientX + 16, y: e.clientY + 16 });
};
return (
<div
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
onMouseMove={handleMouseMove}
style={{ position: 'relative', display: 'inline-block' }}
>
<span style={{ cursor: 'pointer', padding: '8px 16px', background: '#f0f0f0' }}>
Hover me
</span>
{visible && (
<div
style={{
position: 'fixed',
left: position.x,
top: position.y,
background: 'white',
border: '1px solid #ccc',
padding: '12px',
borderRadius: '6px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
pointerEvents: 'none',
zIndex: 1000
}}
>
<h4>Tooltip</h4>
<p>This follows your mouse!</p>
</div>
)}
</div>
);
}
function PasswordStrength() {
const [password, setPassword] = useState('');
const getStrength = () => {
if (password.length < 6) return { text: 'Weak', color: 'red' };
if (password.length < 10) return { text: 'Medium', color: 'orange' };
if (/[A-Z]/.test(password) && /[0-9]/.test(password) && /[^A-Za-z0-9]/.test(password)) {
return { text: 'Strong', color: 'green' };
}
return { text: 'Medium', color: 'orange' };
};
const strength = getStrength();
return (
<div>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Enter password"
style={{ width: '300px', padding: '10px' }}
/>
<div style={{ marginTop: '8px', color: strength.color, fontWeight: 'bold' }}>
Strength: {strength.text}
</div>
<div style={{ marginTop: '8px', color: '#666', fontSize: '0.9em' }}>
{password.length} characters
</div>
</div>
);
}
function ManualInfinite() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const fetchPosts = useCallback(async () => {
if (!hasMore || loading) return;
setLoading(true);
try {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
const data = await res.json();
setPosts(prev => [...prev, ...data]);
setHasMore(data.length === 10);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
}, [page, hasMore, loading]);
useEffect(() => {
fetchPosts();
}, [page, fetchPosts]);
const handleScroll = () => {
if (window.innerHeight + document.documentElement.scrollTop >= document.documentElement.offsetHeight - 100) {
if (!loading && hasMore) setPage(p => p + 1);
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [loading, hasMore]);
return (
<div>
{posts.map(post => (
<div key={post.id} style={{ padding: '16px', borderBottom: '1px solid #eee' }}>
<h4>{post.title}</h4>
<p>{post.body.substring(0, 100)}...</p>
</div>
))}
{loading && <p style={{ textAlign: 'center', padding: '20px' }}>Loading...</p>}
{!hasMore && <p style={{ textAlign: 'center', color: '#888' }}>No more posts</p>}
</div>
);
}
function MasonryGrid({ items }) {
return (
<div style={{
columnCount: 4,
columnGap: '16px',
'@media (max-width: 1200px)': { columnCount: 3 },
'@media (max-width: 900px)': { columnCount: 2 },
'@media (max-width: 600px)': { columnCount: 1 }
}}>
{items.map((item, i) => (
<div key={i} style={{
breakInside: 'avoid',
marginBottom: '16px',
background: '#f8f9fa',
borderRadius: '8px',
overflow: 'hidden'
}}>
<img src={item.image} alt="" style={{ width: '100%', display: 'block' }} />
<p style={{ padding: '12px' }}>{item.title}</p>
</div>
))}
</div>
);
}
// Note: For production, consider react-masonry-css or masonry layout library
function ToastContainer() {
const [toasts, setToasts] = useState([]);
const addToast = (message, type = 'info') => {
const id = Date.now();
setToasts(prev => [...prev, { id, message, type }]);
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 4000);
};
return (
<>
<div style={{ position: 'fixed', top: '16px', right: '16px', zIndex: 1000 }}>
{toasts.map(t => (
<div
key={t.id}
style={{
background: t.type === 'error' ? '#f44336' : t.type === 'success' ? '#4caf50' : '#2196f3',
color: 'white',
padding: '12px 20px',
marginBottom: '8px',
borderRadius: '6px',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)'
}}
>
{t.message}
</div>
))}
</div>
<button onClick={() => addToast('Success! Item added.', 'success')}>Show Success</button>
<button onClick={() => addToast('Error occurred!', 'error')}>Show Error</button>
</>
);
}
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => {
const down = e => {
if (e.key === targetKey) setKeyPressed(true);
};
const up = e => {
if (e.key === targetKey) setKeyPressed(false);
};
window.addEventListener('keydown', down);
window.addEventListener('keyup', up);
return () => {
window.removeEventListener('keydown', down);
window.removeEventListener('keyup', up);
};
}, [targetKey]);
return keyPressed;
}
// Usage:
// const escPressed = useKeyPress('Escape');
// useEffect(() => { if (escPressed) onClose(); }, [escPressed]);
function Comment({ comment, depth = 0 }) {
const [showReplies, setShowReplies] = useState(true);
return (
<div style={{ marginLeft: `${depth * 24}px`, marginBottom: '16px', borderLeft: depth ? '2px solid #ddd' : 'none', paddingLeft: depth ? '16px' : '0' }}>
<p><strong>{comment.author}</strong>: {comment.text}</p>
{comment.replies?.length > 0 && (
<>
<button onClick={() => setShowReplies(!showReplies)} style={{ fontSize: '0.9em' }}>
{showReplies ? 'Hide' : 'Show'} {comment.replies.length} replies
</button>
{showReplies && (
<div style={{ marginTop: '8px' }}>
{comment.replies.map((reply, i) => (
<Comment key={i} comment={reply} depth={depth + 1} />
))}
</div>
)}
</>
)}
</div>
);
}
// Usage: <Comment comment={sampleThread} />
function ColorPicker() {
const [color, setColor] = useState('#ff4081');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div style={{
width: '120px',
height: '120px',
backgroundColor: color,
borderRadius: '12px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
}} />
<input
type="color"
value={color}
onChange={e => setColor(e.target.value)}
/>
<input
type="text"
value={color}
onChange={e => {
const val = e.target.value;
if (/^#[0-9A-Fa-f]{6}$/.test(val)) setColor(val);
}}
placeholder="#ff4081"
maxLength={7}
/>
<p>Selected: {color}</p>
</div>
);
}
function DualRangeSlider({ min = 0, max = 100, step = 1 }) {
const [low, setLow] = useState(min);
const [high, setHigh] = useState(max);
const handleLow = e => {
const val = Math.min(Number(e.target.value), high - step);
setLow(val);
};
const handleHigh = e => {
const val = Math.max(Number(e.target.value), low + step);
setHigh(val);
};
return (
<div style={{ padding: '20px', maxWidth: '400px' }}>
<div style={{ position: 'relative', height: '40px' }}>
<input
type="range"
min={min}
max={max}
step={step}
value={low}
onChange={handleLow}
style={{ position: 'absolute', width: '100%', zIndex: 1 }}
/>
<input
type="range"
min={min}
max={max}
step={step}
value={high}
onChange={handleHigh}
style={{ position: 'absolute', width: '100%', zIndex: 2, opacity: 0.9 }}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '8px' }}>
<span>{low}</span>
<span>{high}</span>
</div>
</div>
);
}
function SortableTable({ data }) {
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
const sortedData = React.useMemo(() => {
if (!sortConfig.key) return data;
return [...data].sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) return sortConfig.direction === 'asc' ? -1 : 1;
if (a[sortConfig.key] > b[sortConfig.key]) return sortConfig.direction === 'asc' ? 1 : -1;
return 0;
});
}, [data, sortConfig]);
const requestSort = key => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
return (
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{Object.keys(data[0] || {}).map(key => (
<th
key={key}
onClick={() => requestSort(key)}
style={{ padding: '12px', cursor: 'pointer', background: '#f0f0f0' }}
>
{key} {sortConfig.key === key ? (sortConfig.direction === 'asc' ? '↑' : '↓') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, i) => (
<tr key={i}>
{Object.values(row).map((val, j) => (
<td key={j} style={{ padding: '12px', borderBottom: '1px solid #eee' }}>{val}</td>
))}
</tr>
))}
</tbody>
</table>
);
}
function SplitPane({ left, right }) {
const [width, setWidth] = useState(50); // percentage
const dividerRef = useRef(null);
useEffect(() => {
const handleMove = e => {
if (!dividerRef.current) return;
const container = dividerRef.current.parentElement;
const rect = container.getBoundingClientRect();
const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
setWidth(Math.max(10, Math.min(90, newWidth)));
};
const handleUp = () => {
document.removeEventListener('mousemove', handleMove);
document.removeEventListener('mouseup', handleUp);
};
const handleDown = () => {
document.addEventListener('mousemove', handleMove);
document.addEventListener('mouseup', handleUp);
};
const divider = dividerRef.current;
divider?.addEventListener('mousedown', handleDown);
return () => divider?.removeEventListener('mousedown', handleDown);
}, []);
return (
<div style={{ display: 'flex', height: '400px', width: '100%' }}>
<div style={{ width: `${width}%`, background: '#f0f8ff', padding: '16px', overflow: 'auto' }}>
{left}
</div>
<div
ref={dividerRef}
style={{
width: '8px',
background: '#ccc',
cursor: 'col-resize',
userSelect: 'none'
}}
/>
<div style={{ width: `${100 - width}%`, background: '#fff0f0', padding: '16px', overflow: 'auto' }}>
{right}
</div>
</div>
);
}
// Usage: <SplitPane left={ } right={ } />
function useOnline() {
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 isOnline = useOnline();
// <p>You are currently {isOnline ? 'online' : 'offline'}</p>
function LazyImage({ src, alt, placeholder = 'https://via.placeholder.com/300x200' }) {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setLoaded(true);
observer.disconnect();
}
},
{ rootMargin: '100px' }
);
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={loaded ? src : placeholder}
alt={alt}
style={{ width: '100%', height: 'auto', transition: 'opacity 0.3s' }}
onLoad={() => setLoaded(true)}
/>
);
}
// Usage: <LazyImage src="real-image.jpg" alt="description" />
function FileUploader() {
const [files, setFiles] = useState([]);
const [progress, setProgress] = useState({});
const handleFiles = e => {
const selected = Array.from(e.target.files);
setFiles(prev => [...prev, ...selected]);
};
const uploadFile = async file => {
const id = file.name + Date.now();
setProgress(prev => ({ ...prev, [id]: 0 }));
// simulate upload
return new Promise(resolve => {
let pct = 0;
const interval = setInterval(() => {
pct += 10;
setProgress(prev => ({ ...prev, [id]: pct }));
if (pct >= 100) {
clearInterval(interval);
resolve();
}
}, 300);
});
};
const startUpload = () => {
files.forEach(file => uploadFile(file));
};
return (
<div>
<input type="file" multiple onChange={handleFiles} />
<ul>
{files.map((file, i) => (
<li key={i}>
{file.name} – {progress[file.name + i] || 0}%
</li>
))}
</ul>
<button onClick={startUpload} disabled={!files.length}>Upload All</button>
</div>
);
}
function ChatInput({ onSend }) {
const [message, setMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const timeoutRef = useRef(null);
const handleChange = (e) => {
const text = e.target.value;
setMessage(text);
// Show "typing..." immediately
setIsTyping(true);
// Clear previous timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Hide "typing..." after 2 seconds of inactivity
timeoutRef.current = setTimeout(() => {
setIsTyping(false);
}, 2000);
};
const handleSend = () => {
if (!message.trim()) return;
onSend(message.trim());
setMessage('');
setIsTyping(false);
// Clear any pending timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
return (
<div style={{ position: 'relative', padding: '12px', background: '#f8f9fa', borderTop: '1px solid #ddd' }}>
<textarea
value={message}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Type a message..."
rows={1}
style={{
width: '100%',
minHeight: '44px',
padding: '12px',
borderRadius: '20px',
border: '1px solid #ccc',
resize: 'none',
fontSize: '16px'
}}
/>
{isTyping && message.trim() && (
<small
style={{
position: 'absolute',
bottom: '4px',
left: '24px',
color: '#666',
fontSize: '0.85em',
pointerEvents: 'none'
}}
>
Typing...
</small>
)}
<button
onClick={handleSend}
disabled={!message.trim()}
style={{
position: 'absolute',
right: '20px',
bottom: '20px',
padding: '8px 16px',
background: message.trim() ? '#0084ff' : '#ccc',
color: 'white',
border: 'none',
borderRadius: '20px',
cursor: message.trim() ? 'pointer' : 'not-allowed'
}}
>
Send
</button>
</div>
);
}
// Example parent usage:
// function Chat() {
// const [messages, setMessages] = useState([]);
// const handleSend = (text) => setMessages(prev => [...prev, { text, from: 'me' }]);
// return (
// <>
// <div style={{ height: '400px', overflowY: 'auto' }}>{messages.map((m,i) => <p key={i}>{m.text}</p>)}</div>
// <ChatInput onSend={handleSend} />
// </>
// );
// }
function useThrottle(callback, delay = 300) {
const lastCallTime = useRef(0);
const timeoutId = useRef(null);
const throttledFunction = useCallback((...args) => {
const now = Date.now();
// If enough time has passed since last execution → run immediately
if (now - lastCallTime.current >= delay) {
callback(...args);
lastCallTime.current = now;
// Clear any pending timeout
if (timeoutId.current) {
clearTimeout(timeoutId.current);
timeoutId.current = null;
}
}
// Otherwise schedule it for the end of the current delay window
else if (!timeoutId.current) {
timeoutId.current = setTimeout(() => {
callback(...args);
lastCallTime.current = Date.now();
timeoutId.current = null;
}, delay - (now - lastCallTime.current));
}
}, [callback, delay]);
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (timeoutId.current) {
clearTimeout(timeoutId.current);
}
};
}, []);
return throttledFunction;
}
// ──────────────────────────────────────────────
// Example usage: throttle scroll handler
// ──────────────────────────────────────────────
function ThrottledScrollDemo() {
const handleScroll = useThrottle(() => {
console.log('Scrolled! Current scrollY:', window.scrollY);
}, 400);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
return (
<div style={{ height: '2000px', background: 'linear-gradient(to bottom, #f0f8ff, #e6f2ff)' }}>
<h2 style={{ position: 'fixed', top: '20px', left: '20px', background: 'white', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
Scroll me ↓ (console logs throttled to ~every 400ms)
</h2>
</div>
);
}