Master React hooks: useState, useEffect, useContext, and custom hooks.
Hooks are functions that let you "hook into" React state and lifecycle features from function components.
Manages local state in functional components.
function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>; }
Handles side effects in functional components.
function DataFetcher() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/data') .then((res) => res.json()) .then(setData); }, []); // Empty dependency array = run once }
Loading diagram...
function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { return initialValue; } }); const setValue = (value) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.log(error); } }; return [storedValue, setValue]; }
Understanding when hooks re-run is crucial:
// Runs after every render useEffect(() => { console.log('Effect runs every time'); }); // Runs only once after initial render useEffect(() => { console.log('Effect runs once'); }, []); // Runs when count changes useEffect(() => { console.log('Effect runs when count changes'); }, [count]); // Runs when count or name changes useEffect(() => { console.log('Effect runs when count or name changes'); }, [count, name]);
function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (e) => { const { name, value } = e.target; setValues((prev) => ({ ...prev, [name]: value, })); }; const reset = () => setValues(initialValues); return { values, handleChange, reset }; }
function useApi(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setLoading(true); try { const response = await fetch(url); const result = await response.json(); setData(result); } catch (err) { setError(err.message); } finally { setLoading(false); } }, [url]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; }
Interactive hooks demonstration will go here
Custom hooks let you extract component logic into reusable functions.
function useDataFetching(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const response = await fetch(url); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); setData(result); } catch (err) { setError(err.message); } finally { setLoading(false); } }, [url]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; }
function useCounter(initialState = 0) { const [state, dispatch] = useReducer(counterReducer, initialState); const increment = () => dispatch({ type: 'INCREMENT' }); const decrement = () => dispatch({ type: 'DECREMENT' }); const reset = () => dispatch({ type: 'RESET' }); return { count: state, increment, decrement, reset }; } function counterReducer(state, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; case 'RESET': return 0; default: return state; } }
function ExpensiveComponent({ items, onItemClick }) { // Memoize expensive calculation const expensiveValue = useMemo(() => { return items.reduce((acc, item) => acc + item.value, 0); }, [items]); // Memoize callback to prevent unnecessary re-renders const handleClick = useCallback( (item) => { onItemClick(item); }, [onItemClick] ); return ( <div> <p>Total: {expensiveValue}</p> {items.map((item) => ( <button key={item.id} onClick={() => handleClick(item)}> {item.name} </button> ))} </div> ); }
Loading diagram...
function useFocus() { const inputRef = useRef(null); const focus = useCallback(() => { inputRef.current?.focus(); }, []); return { inputRef, focus }; } function InputWithFocus() { const { inputRef, focus } = useFocus(); return ( <div> <input ref={inputRef} placeholder="Type here..." /> <button onClick={focus}>Focus Input</button> </div> ); }
const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), blur: () => inputRef.current.blur(), select: () => inputRef.current.select(), })); return <input ref={inputRef} {...props} />; }); function Parent() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); }; return ( <div> <FancyInput ref={inputRef} /> <button onClick={handleFocus}>Focus</button> </div> ); }
function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useLayoutEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return size; }
import { renderHook, act } from '@testing-library/react'; test('useCounter increments count', () => { const { result } = renderHook(() => useCounter(0)); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); test('useCounter respects initial value', () => { const { result } = renderHook(() => useCounter(5)); expect(result.current.count).toBe(5); });
// ❌ Bad: Too many responsibilities function useUser() { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [settings, setSettings] = useState({}); // ... many more states } // ✅ Good: Single responsibility function useUser() { const [user, setUser] = useState(null); // ... only user-related logic } function useUserPosts() { const [posts, setPosts] = useState([]); // ... only posts-related logic }
// ❌ Bad: Unclear purpose function useData() { ... } // ✅ Good: Clear purpose function useUserProfile() { ... } function usePostComments() { ... }
function useInterval(callback, delay) { useEffect(() => { const id = setInterval(callback, delay); // Always return cleanup function return () => clearInterval(id); }, [callback, delay]); }
function useExpensiveCalculation(data) { return useMemo(() => { return data.reduce((acc, item) => { // Expensive calculation here return acc + complexCalculation(item); }, 0); }, [data]); }
import { useForm } from 'react-hook-form'; function Form() { const { register, handleSubmit, formState: { errors }, } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('firstName', { required: true })} /> {errors.firstName && <span>This field is required</span>} <button type="submit">Submit</button> </form> ); }
import { useQuery } from '@tanstack/react-query'; function Posts() { const { data, isLoading, error } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <div> {data.map((post) => ( <Post key={post.id} post={post} /> ))} </div> ); }