Hooks Deep Dive

Hooks let you use state and other React features in function components. They replaced class component lifecycle methods with a cleaner, more composable API.

useState

JSX
const [value, setValue] = useState(initialValue);

// Lazy initialization (expensive computation)
const [data, setData] = useState(() => {
    return JSON.parse(localStorage.getItem('data')) || [];
});

// Multiple state variables
const [name, setName] = useState('');
const [age, setAge] = useState(0);
// OR combine into one object
const [form, setForm] = useState({ name: '', age: 0 });

useEffect

Runs side effects after render. Replaces componentDidMount, componentDidUpdate, and componentWillUnmount.

JSX
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        let cancelled = false;

        async function fetchUser() {
            setLoading(true);
            try {
                const response = await fetch(`/api/users/${userId}`);
                const data = await response.json();
                if (!cancelled) {
                    setUser(data);
                    setLoading(false);
                }
            } catch (err) {
                if (!cancelled) setLoading(false);
            }
        }

        fetchUser();

        // Cleanup function (like componentWillUnmount)
        return () => { cancelled = true; };
    }, [userId]);  // Re-run when userId changes

    if (loading) return <p>Loading...</p>;
    if (!user) return <p>User not found</p>;

    return (
        <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
        </div>
    );
}

useEffect Patterns

JSX
// Run once on mount (empty dependency array)
useEffect(() => {
    console.log('Component mounted');
}, []);

// Run on every render (no dependency array)
useEffect(() => {
    console.log('Rendered');
});

// Run when specific values change
useEffect(() => {
    document.title = `Count: ${count}`;
}, [count]);  // Only re-runs when count changes

// Cleanup on unmount or before re-run
useEffect(() => {
    const subscription = someService.subscribe(handleEvent);
    return () => subscription.unsubscribe();
}, []);

useRef

Access DOM elements or persist values without causing re-reounds.

JSX
function TextInput() {
    const inputRef = useRef(null);

    const focusInput = () => {
        inputRef.current.focus();
    };

    return (
        <>
            <input ref={inputRef} type="text" />
            <button onClick={focusInput}>Focus Input</button>
        </>
    );
}

// Persist value across renders
function Stopwatch() {
    const [time, setTime] = useState(0);
    const intervalRef = useRef(null);

    const start = () => {
        intervalRef.current = setInterval(() => {
            setTime(t => t + 1);
        }, 1000);
    };

    const stop = () => {
        clearInterval(intervalRef.current);
    };

    return (
        <div>
            <p>{time}s</p>
            <button onClick={start}>Start</button>
            <button onClick={stop}>Stop</button>
        </div>
    );
}

useContext

Access context without prop drilling.

JSX
// Create context
const ThemeContext = createContext('light');

// Provider
function App() {
    const [theme, setTheme] = useState('light');

    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Dashboard />
        </ThemeContext.Provider>
    );
}

// Consumer
function ThemedButton() {
    const { theme, setTheme } = useContext(ThemeContext);

    return (
        <button
            className={theme}
            onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        >
            Toggle Theme
        </button>
    );
}

useMemo and useCallback

JSX
// useMemo — memoize expensive calculations
const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);  // Only re-calculate when items change

// useCallback — memoize functions (prevents child re-renders)
const handleClick = useCallback((id) => {
    setSelectedId(id);
}, []);  // Function identity stays stable

// Pass to child — won't cause unnecessary re-renders
<DataTable onSelect={handleClick} />

Custom Hooks

Extract reusable logic into custom hooks:

JSX
// useFetch.js
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const controller = new AbortController();

        fetch(url, { signal: controller.signal })
            .then(res => res.json())
            .then(data => { setData(data); setLoading(false); })
            .catch(err => {
                if (err.name !== 'AbortError') {
                    setError(err);
                    setLoading(false);
                }
            });

        return () => controller.abort();
    }, [url]);

    return { data, loading, error };
}

// Usage
function UserList() {
    const { data: users, loading, error } = useFetch('/api/users');

    if (loading) return <Spinner />;
    if (error) return <Error message={error.message} />;
    return users.map(u => <UserCard key={u.id} user={u} />);
}

// useLocalStorage
function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        const stored = localStorage.getItem(key);
        return stored !== null ? JSON.parse(stored) : initialValue;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);

    return [value, setValue];
}

// useDebounce
function useDebounce(value, delay) {
    const [debounced, setDebounced] = useState(value);

    useEffect(() => {
        const timer = setTimeout(() => setDebounced(value), delay);
        return () => clearTimeout(timer);
    }, [value, delay]);

    return debounced;
}

💡 Tip: Follow the Rules of Hooks — only call hooks at the top level (not in loops, conditions, or nested functions) and only call them from React functions.

Next: APIs and Data Fetching