import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(/api/users/${userId}
);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]); // Dependency array
if (loading) return Loading...;
if (!user) return User not found;
return (
{user.name}
{user.email}
);
}
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
};
}, []); // Empty dependency array - runs once
return Timer: {seconds}s;
}
function SearchResults({ query, filters }) {
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!query) {
setResults([]);
return;
}
setLoading(true);
const searchParams = new URLSearchParams({
q: query,
...filters
});
fetch(/api/search?${searchParams}
)
.then(response => response.json())
.then(data => {
setResults(data.results);
setLoading(false);
})
.catch(error => {
console.error('Search failed:', error);
setLoading(false);
});
}, [query, filters]); // Re-run when query or filters change
return (
{loading && Searching...}
{results.map(result => (
{result.title}
))}
);
}
function DocumentTitle({ title, shouldUpdate }) {
useEffect(() => {
if (shouldUpdate) {
document.title = title;
}
}, [title, shouldUpdate]);
return null;
}
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// Perform search
console.log('Searching for:', debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
Current: {count}
Previous: {prevCount}
);
}
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch(url, {
...options,
signal: abortController.signal
});
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => {
abortController.abort();
};
}, [url, JSON.stringify(options)]);
return { data, loading, error };
}
// Usage
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return Loading users...;
if (error) return Error: {error};
return (
{users?.map(user => (
- {user.name}
))}
);
}
function useLocalStorage(key, initialValue) {
// Get from local storage then parse stored json or return initialValue
const [storedValue, setStoredValue] = useState(() => {
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 = useCallback((value) => {
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
);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'en');
return (
);
}
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
handleResize(); // Call handler right away so state gets updated with initial window size
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
// Usage
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
Window size: {width} x {height}
{width < 768 ? (
) : (
)}
);
}
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = useCallback((name, value) => {
setValues(prev => ({
...prev,
[name]: value
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: undefined
}));
}
}, [errors]);
const handleBlur = useCallback((name) => {
setTouched(prev => ({
...prev,
[name]: true
}));
if (validate) {
const fieldErrors = validate({ [name]: values[name] });
setErrors(prev => ({
...prev,
...fieldErrors
}));
}
}, [values, validate]);
const handleSubmit = useCallback((onSubmit) => {
return (e) => {
e.preventDefault();
if (validate) {
const formErrors = validate(values);
setErrors(formErrors);
if (Object.keys(formErrors).length === 0) {
onSubmit(values);
}
} else {
onSubmit(values);
}
};
}, [values, validate]);
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
return {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
reset
};
}
// Usage
function ContactForm() {
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Name is required';
}
if (!values.email) {
errors.email = 'Email is required';
} else if (!/S+@S+.S+/.test(values.email)) {
errors.email = 'Email is invalid';
}
return errors;
};
const {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
reset
} = useForm({ name: '', email: '', message: '' }, validate);
const onSubmit = (formData) => {
console.log('Form submitted:', formData);
reset();
};
return (
);
}
// ❌ Bad - missing dependency
function BadExample({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Missing userId dependency
return {user?.name};
}
// ✅ Good - all dependencies included
function GoodExample({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // userId included
return {user?.name};
}
function UserDashboard({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// Separate effect for user data
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// Separate effect for posts data
useEffect(() => {
fetchUserPosts(userId).then(setPosts);
}, [userId]);
return (
);
}
function WebSocketComponent() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cleanup function
return () => {
ws.close();
};
}, []);
return (
{messages.map((msg, index) => (
{msg.text}
))}
);
}